diff --git a/.eslintignore b/.eslintignore index 25dff64df3..22a60fa754 100644 --- a/.eslintignore +++ b/.eslintignore @@ -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 diff --git a/.eslintrc.js b/.eslintrc.js index 46a94e7740..47c8f72b55 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -300,6 +300,12 @@ module.exports = { 'local-rules/type-alias-readonlydeep': 'error', }, }, + { + files: ['ts/**/*_test.{ts,tsx}'], + rules: { + 'func-names': 'off', + }, + }, ], rules: { diff --git a/.github/workflows/stories.yml b/.github/workflows/stories.yml new file mode 100644 index 0000000000..d5ae8d77ae --- /dev/null +++ b/.github/workflows/stories.yml @@ -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 diff --git a/.storybook/main.js b/.storybook/main.js deleted file mode 100644 index 3310856f67..0000000000 --- a/.storybook/main.js +++ /dev/null @@ -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, - }, -}; diff --git a/.storybook/main.ts b/.storybook/main.ts new file mode 100644 index 0000000000..ebda446b13 --- /dev/null +++ b/.storybook/main.ts @@ -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; diff --git a/.storybook/preview-head.html b/.storybook/preview-head.html deleted file mode 100644 index d9cd9e8843..0000000000 --- a/.storybook/preview-head.html +++ /dev/null @@ -1,63 +0,0 @@ - - - - - - - diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index ae3ec18f16..06afdc5784 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -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 = 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 ( + + + + ); +} + +function withScrollLockProvider(Story, context) { + return ( + {})} + > + + + ); +} + +export const decorators = [ + withModeAndThemeProvider, + withMockStoreProvider, + withScrollLockProvider, +]; export const parameters = { axe: { diff --git a/.storybook/webpack.config.js b/.storybook/webpack.config.js deleted file mode 100644 index c11fdbe046..0000000000 --- a/.storybook/webpack.config.js +++ /dev/null @@ -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; -}; diff --git a/.yarnclean b/.yarnclean index 4593ef707b..230d0afc65 100644 --- a/.yarnclean +++ b/.yarnclean @@ -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 diff --git a/package.json b/package.json index cc393d360b..f54e3f8553 100644 --- a/package.json +++ b/package.json @@ -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" }, diff --git a/patches/@storybook+manager-api+7.4.5.patch b/patches/@storybook+manager-api+7.4.5.patch new file mode 100644 index 0000000000..ecba613a45 --- /dev/null +++ b/patches/@storybook+manager-api+7.4.5.patch @@ -0,0 +1,13 @@ +diff --git a/node_modules/@storybook/manager-api/dist/index.d.ts b/node_modules/@storybook/manager-api/dist/index.d.ts +index 7afb86e..a15b801 100644 +--- a/node_modules/@storybook/manager-api/dist/index.d.ts ++++ b/node_modules/@storybook/manager-api/dist/index.d.ts +@@ -822,7 +822,7 @@ declare class ManagerProvider extends Component { + static getDerivedStateFromProps(props: ManagerProviderProps, state: State): State; + shouldComponentUpdate(nextProps: ManagerProviderProps, nextState: State): boolean; + initModules: () => void; +- render(): React.JSX.Element; ++ render(): JSX.Element; + } + interface ManagerConsumerProps

{ + filter?: (combo: Combo) => P; diff --git a/patches/@storybook+router+7.4.5.patch b/patches/@storybook+router+7.4.5.patch new file mode 100644 index 0000000000..793a975dbe --- /dev/null +++ b/patches/@storybook+router+7.4.5.patch @@ -0,0 +1,22 @@ +diff --git a/node_modules/@storybook/router/dist/index.d.ts b/node_modules/@storybook/router/dist/index.d.ts +index ed699f7..71f48eb 100644 +--- a/node_modules/@storybook/router/dist/index.d.ts ++++ b/node_modules/@storybook/router/dist/index.d.ts +@@ -335,7 +335,7 @@ declare const useNavigate: () => (to: To | number, { plain, ...options }?: any) + * A component that will navigate to a new location/path when clicked + */ + declare const Link: { +- ({ to, children, ...rest }: LinkProps): React__default.JSX.Element; ++ ({ to, children, ...rest }: LinkProps): JSX.Element; + displayName: string; + }; + /** +@@ -343,7 +343,7 @@ declare const Link: { + * and will be called whenever it changes when it changes + */ + declare const Location: { +- ({ children }: LocationProps): React__default.JSX.Element; ++ ({ children }: LocationProps): JSX.Element; + displayName: string; + }; + /** diff --git a/patches/@types+backbone+1.4.16.patch b/patches/@types+backbone+1.4.16.patch new file mode 100644 index 0000000000..ed2430deb1 --- /dev/null +++ b/patches/@types+backbone+1.4.16.patch @@ -0,0 +1,49 @@ +diff --git a/node_modules/@types/backbone/index.d.ts b/node_modules/@types/backbone/index.d.ts +index 3935075..22a4c2e 100644 +--- a/node_modules/@types/backbone/index.d.ts ++++ b/node_modules/@types/backbone/index.d.ts +@@ -81,7 +81,7 @@ declare namespace Backbone { + collection?: Collection | undefined; + } + +- type CombinedModelConstructorOptions = Model> = ModelConstructorOptions & E; ++ type CombinedModelConstructorOptions = Model> = ModelConstructorOptions & E; + + interface ModelSetOptions extends Silenceable, Validable {} + +@@ -219,7 +219,7 @@ declare namespace Backbone { + */ + static extend(properties: any, classProperties?: any): any; + +- attributes: Partial; ++ attributes: T; + changed: Partial; + cidPrefix: string; + cid: string; +@@ -235,7 +235,7 @@ declare namespace Backbone { + * That works only if you set it in the constructor or the initialize method. + */ + defaults(): Partial; +- id: string | number; ++ id: string; + idAttribute: string; + validationError: any; + +@@ -266,7 +266,7 @@ declare namespace Backbone { + * return super.get("name"); + * } + */ +- get>(attributeName: A): T[A] | undefined; ++ get>(attributeName: A): T[A]; + + /** + * For strongly-typed assignment of attributes, use the `set` method only privately in public setter properties. +@@ -300,7 +300,7 @@ declare namespace Backbone { + previousAttributes(): Partial; + save(attributes?: Partial | null, options?: ModelSaveOptions): JQueryXHR; + unset(attribute: _StringKey, options?: Silenceable): this; +- validate(attributes: Partial, options?: any): any; ++ validate(attributes: T, options?: any): any; + private _validate(attributes: Partial, options: any): boolean; + + // mixins from underscore diff --git a/patches/@types+backbone+1.4.5.patch b/patches/@types+backbone+1.4.5.patch deleted file mode 100644 index 57bfcd8a8a..0000000000 --- a/patches/@types+backbone+1.4.5.patch +++ /dev/null @@ -1,30 +0,0 @@ -diff --git a/node_modules/@types/backbone/index.d.ts b/node_modules/@types/backbone/index.d.ts -index a172230..2c62e92 100644 ---- a/node_modules/@types/backbone/index.d.ts -+++ b/node_modules/@types/backbone/index.d.ts -@@ -81,7 +81,7 @@ declare namespace Backbone { - collection?: Backbone.Collection; - } - -- type CombinedModelConstructorOptions = Model> = ModelConstructorOptions & E; -+ type CombinedModelConstructorOptions = Model> = ModelConstructorOptions & E; - - interface ModelSetOptions extends Silenceable, Validable { - } -@@ -218,14 +218,14 @@ declare namespace Backbone { - * E - Extensions to the model constructor options. You can accept additional constructor options - * by listing them in the E parameter. - */ -- class Model extends ModelBase implements Events { -+ class Model = any, S = Backbone.ModelSetOptions, E = {}> extends ModelBase implements Events { - - /** - * Do not use, prefer TypeScript's extend functionality. - **/ - public static extend(properties: any, classProperties?: any): any; - -- attributes: any; -+ attributes: T; - changed: any[]; - cidPrefix: string; - cid: string; diff --git a/patches/@types+express+4.17.13.patch b/patches/@types+express+4.17.18.patch similarity index 100% rename from patches/@types+express+4.17.13.patch rename to patches/@types+express+4.17.18.patch diff --git a/patches/@types+jest+28.1.1.patch b/patches/@types+jest+28.1.3.patch similarity index 94% rename from patches/@types+jest+28.1.1.patch rename to patches/@types+jest+28.1.3.patch index b33bddd34a..77ea181e8e 100644 --- a/patches/@types+jest+28.1.1.patch +++ b/patches/@types+jest+28.1.3.patch @@ -1,10 +1,10 @@ diff --git a/node_modules/@types/jest/index.d.ts b/node_modules/@types/jest/index.d.ts -index 272822a..87ebd1f 100755 +index b37b188..1908c0c 100755 --- a/node_modules/@types/jest/index.d.ts +++ b/node_modules/@types/jest/index.d.ts @@ -30,18 +30,18 @@ // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped - // Minimum TypeScript Version: 3.8 + // Minimum TypeScript Version: 4.3 -declare var beforeAll: jest.Lifecycle; -declare var beforeEach: jest.Lifecycle; diff --git a/ts/Crypto.ts b/ts/Crypto.ts index 09cee51db9..ceb3273b70 100644 --- a/ts/Crypto.ts +++ b/ts/Crypto.ts @@ -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 diff --git a/ts/background.ts b/ts/background.ts index add1673591..ff7316ef79 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -3288,13 +3288,14 @@ export async function startApp(): Promise { '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, diff --git a/ts/components/AddGroupMemberErrorDialog.stories.tsx b/ts/components/AddGroupMemberErrorDialog.stories.tsx index 9ced0f47f7..5db6945b6c 100644 --- a/ts/components/AddGroupMemberErrorDialog.stories.tsx +++ b/ts/components/AddGroupMemberErrorDialog.stories.tsx @@ -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; const defaultProps = { i18n, onClose: action('onClose'), }; -export const _MaximumGroupSize = (): JSX.Element => ( - -); - -_MaximumGroupSize.story = { - name: 'Maximum group size', -}; +export function MaximumGroupSize(): JSX.Element { + return ( + + ); +} export function MaximumRecommendedGroupSize(): JSX.Element { return ( @@ -44,7 +44,3 @@ export function MaximumRecommendedGroupSize(): JSX.Element { /> ); } - -MaximumRecommendedGroupSize.story = { - name: 'Maximum recommended group size', -}; diff --git a/ts/components/AddGroupMemberErrorDialog.tsx b/ts/components/AddGroupMemberErrorDialog.tsx index cae318cfa2..7cb81962dc 100644 --- a/ts/components/AddGroupMemberErrorDialog.tsx +++ b/ts/components/AddGroupMemberErrorDialog.tsx @@ -23,7 +23,7 @@ type PropsDataType = recommendedMaximumNumberOfContacts: number; }; -type PropsType = { +export type PropsType = { i18n: LocalizerType; onClose: () => void; } & PropsDataType; diff --git a/ts/components/AddUserToAnotherGroupModal.stories.tsx b/ts/components/AddUserToAnotherGroupModal.stories.tsx index 4848542fa9..b3ffe7e607 100644 --- a/ts/components/AddUserToAnotherGroupModal.stories.tsx +++ b/ts/components/AddUserToAnotherGroupModal.stories.tsx @@ -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; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ( - -); +const Template: StoryFn = args => { + return ( + + ); +}; export const Modal = Template.bind({}); -Modal.args = {}; diff --git a/ts/components/Alert.stories.tsx b/ts/components/Alert.stories.tsx index d590dcef9f..272b6af0d4 100644 --- a/ts/components/Alert.stories.tsx +++ b/ts/components/Alert.stories.tsx @@ -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; 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 ( ); } - -LongBodyWithTitle.story = { - name: 'Long body (with title)', -}; diff --git a/ts/components/Alert.tsx b/ts/components/Alert.tsx index c9ac714b7d..56eec431d7 100644 --- a/ts/components/Alert.tsx +++ b/ts/components/Alert.tsx @@ -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; diff --git a/ts/components/AnimatedEmojiGalore.stories.tsx b/ts/components/AnimatedEmojiGalore.stories.tsx index 491e2eec03..acc4edc314 100644 --- a/ts/components/AnimatedEmojiGalore.stories.tsx +++ b/ts/components/AnimatedEmojiGalore.stories.tsx @@ -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; function getDefaultProps(): PropsType { return { diff --git a/ts/components/Avatar.stories.tsx b/ts/components/Avatar.stories.tsx index ec29c5cad1..d09181694d 100644 --- a/ts/components/Avatar.stories.tsx +++ b/ts/components/Avatar.stories.tsx @@ -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; const createProps = (overrideProps: Partial = {}): Props => ({ acceptedMessageRequest: isBoolean(overrideProps.acceptedMessageRequest) @@ -87,7 +88,7 @@ const createProps = (overrideProps: Partial = {}): 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; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ( - <> - {sizes.map(size => ( - - ))} - -); +const Template: StoryFn = (args: Props) => { + return ( + <> + {sizes.map(size => ( + + ))} + + ); +}; // eslint-disable-next-line react/function-component-definition -const TemplateSingle: Story = args => ( +const TemplateSingle: StoryFn = (args: Props) => ( ); @@ -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', -}; diff --git a/ts/components/AvatarColorPicker.stories.tsx b/ts/components/AvatarColorPicker.stories.tsx index c66e745de5..43e03caa33 100644 --- a/ts/components/AvatarColorPicker.stories.tsx +++ b/ts/components/AvatarColorPicker.stories.tsx @@ -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 => ({ export default { title: 'Components/AvatarColorPicker', -}; +} satisfies Meta; export function Default(): JSX.Element { return ; diff --git a/ts/components/AvatarEditor.stories.tsx b/ts/components/AvatarEditor.stories.tsx index f4ef76f540..6301b4c233 100644 --- a/ts/components/AvatarEditor.stories.tsx +++ b/ts/components/AvatarEditor.stories.tsx @@ -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 => ({ export default { title: 'Components/AvatarEditor', -}; +} satisfies Meta; 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 ( ); } -NoAvatarMe.story = { - name: 'No Avatar (me)', -}; - export function HasAvatar(): JSX.Element { return ( = {}): PropsType => ({ export default { title: 'Components/AvatarIconEditor', -}; +} satisfies Meta; export function PersonalIcon(): JSX.Element { return ( diff --git a/ts/components/AvatarLightbox.stories.tsx b/ts/components/AvatarLightbox.stories.tsx index c06b798481..a9e5bdc70c 100644 --- a/ts/components/AvatarLightbox.stories.tsx +++ b/ts/components/AvatarLightbox.stories.tsx @@ -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 => ({ - 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; -export function Group(): JSX.Element { - return ( - - ); +export function Group(args: PropsType): JSX.Element { + return ; } -export function Person(): JSX.Element { +export function Person(args: PropsType): JSX.Element { const conversation = getDefaultConversation(); return ( ); } -export function Photo(): JSX.Element { - return ( - - ); +export function Photo(args: PropsType): JSX.Element { + return ; } diff --git a/ts/components/AvatarModalButtons.stories.tsx b/ts/components/AvatarModalButtons.stories.tsx index d4488f6645..10005459ee 100644 --- a/ts/components/AvatarModalButtons.stories.tsx +++ b/ts/components/AvatarModalButtons.stories.tsx @@ -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 => ({ export default { title: 'Components/AvatarModalButtons', -}; +} satisfies Meta; 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 ; } - -NoChanges.story = { - name: 'No changes', -}; diff --git a/ts/components/AvatarPreview.stories.tsx b/ts/components/AvatarPreview.stories.tsx index 3868ef0c40..bedde737a8 100644 --- a/ts/components/AvatarPreview.stories.tsx +++ b/ts/components/AvatarPreview.stories.tsx @@ -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 => ({ export default { title: 'Components/AvatarPreview', -}; +} satisfies Meta; 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 ( ; } -Value.story = { - name: 'value', -}; - export function Path(): JSX.Element { return ( ); } - -Style.story = { - name: 'style', -}; diff --git a/ts/components/AvatarTextEditor.stories.tsx b/ts/components/AvatarTextEditor.stories.tsx index 3d113be248..8058bb7764 100644 --- a/ts/components/AvatarTextEditor.stories.tsx +++ b/ts/components/AvatarTextEditor.stories.tsx @@ -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 => ({ export default { title: 'Components/AvatarTextEditor', -}; +} satisfies Meta; export function Empty(): JSX.Element { return ; @@ -42,10 +43,6 @@ export function WithData(): JSX.Element { ); } -WithData.story = { - name: 'with Data', -}; - export function WithWideCharacters(): JSX.Element { return ( ); } - -WithWideCharacters.story = { - name: 'with wide characters', -}; diff --git a/ts/components/AvatarUploadButton.stories.tsx b/ts/components/AvatarUploadButton.stories.tsx index 1f4aed996b..6a866de048 100644 --- a/ts/components/AvatarUploadButton.stories.tsx +++ b/ts/components/AvatarUploadButton.stories.tsx @@ -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 => ({ export default { title: 'Components/AvatarUploadButton', -}; +} satisfies Meta; export function Default(): JSX.Element { return ; diff --git a/ts/components/BadgeDescription.stories.tsx b/ts/components/BadgeDescription.stories.tsx index 5edb8e2d51..fe9b1a8f14 100644 --- a/ts/components/BadgeDescription.stories.tsx +++ b/ts/components/BadgeDescription.stories.tsx @@ -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; 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 ( ); } - -NameWithRtlOverrides.story = { - name: 'Name with RTL overrides', -}; diff --git a/ts/components/BadgeDescription.tsx b/ts/components/BadgeDescription.tsx index e5343fa927..5fee7f4a02 100644 --- a/ts/components/BadgeDescription.tsx +++ b/ts/components/BadgeDescription.tsx @@ -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 = []; let lastIndex = 0; diff --git a/ts/components/BadgeDialog.stories.tsx b/ts/components/BadgeDialog.stories.tsx index 36ff3b2dcc..c45deeea0b 100644 --- a/ts/components/BadgeDialog.stories.tsx +++ b/ts/components/BadgeDialog.stories.tsx @@ -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; const defaultProps: ComponentProps = { areWeASubscriber: false, @@ -31,18 +33,10 @@ export function NoBadgesClosedImmediately(): JSX.Element { return ; } -NoBadgesClosedImmediately.story = { - name: 'No badges (closed immediately)', -}; - export function OneBadge(): JSX.Element { return ; } -OneBadge.story = { - name: 'One badge', -}; - export function BadgeWithNoImageShouldBeImpossible(): JSX.Element { return ( ; } -FiveBadges.story = { - name: 'Five badges', -}; - export function ManyBadges(): JSX.Element { return ; } -ManyBadges.story = { - name: 'Many badges', -}; - export function ManyBadgesUserIsASubscriber(): JSX.Element { return ( ); } - -ManyBadgesUserIsASubscriber.story = { - name: 'Many badges, user is a subscriber', -}; diff --git a/ts/components/BadgeDialog.tsx b/ts/components/BadgeDialog.tsx index 2224d6e761..fafc7c7b49 100644 --- a/ts/components/BadgeDialog.tsx +++ b/ts/components/BadgeDialog.tsx @@ -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; firstName?: string; diff --git a/ts/components/BetterAvatar.stories.tsx b/ts/components/BetterAvatar.stories.tsx index e07cae81c4..6a15a926f6 100644 --- a/ts/components/BetterAvatar.stories.tsx +++ b/ts/components/BetterAvatar.stories.tsx @@ -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 => ({ export default { title: 'Components/BetterAvatar', -}; +} satisfies Meta; export function Text(): JSX.Element { return ( diff --git a/ts/components/BetterAvatarBubble.stories.tsx b/ts/components/BetterAvatarBubble.stories.tsx index b4921bc95d..c4fa5f1ded 100644 --- a/ts/components/BetterAvatarBubble.stories.tsx +++ b/ts/components/BetterAvatarBubble.stories.tsx @@ -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 => ({ export default { title: 'Components/BetterAvatarBubble', -}; +} satisfies Meta; export function Children(): JSX.Element { return ( diff --git a/ts/components/Button.stories.tsx b/ts/components/Button.stories.tsx index 7ad08a7f3e..c745533933 100644 --- a/ts/components/Button.stories.tsx +++ b/ts/components/Button.stories.tsx @@ -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; 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 ( ); } - -CustomStyles.story = { - name: 'Custom styles', -}; diff --git a/ts/components/Button.tsx b/ts/components/Button.tsx index ce39c7bb45..36dee96f50 100644 --- a/ts/components/Button.tsx +++ b/ts/components/Button.tsx @@ -40,7 +40,7 @@ export enum ButtonIconType { video = 'video', } -type PropsType = { +export type PropsType = { className?: string; disabled?: boolean; icon?: ButtonIconType; diff --git a/ts/components/CallManager.stories.tsx b/ts/components/CallManager.stories.tsx index 4a176796a3..a9d43496c3 100644 --- a/ts/components/CallManager.stories.tsx +++ b/ts/components/CallManager.stories.tsx @@ -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 => ({ @@ -83,12 +73,8 @@ const createProps = (storyProps: Partial = {}): 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 => ({ export default { title: 'Components/CallManager', -}; + argTypes: {}, + args: {}, +} satisfies Meta; export function NoCall(): JSX.Element { return ; @@ -184,10 +172,6 @@ export function RingingDirectCall(): JSX.Element { ); } -RingingDirectCall.story = { - name: 'Ringing (direct call)', -}; - export function RingingGroupCall(): JSX.Element { return ( ); } - -GroupCallSafetyNumberChanged.story = { - name: 'Group call - Safety Number Changed', -}; diff --git a/ts/components/CallScreen.stories.tsx b/ts/components/CallScreen.stories.tsx index 1af74d0cc9..06e699243d 100644 --- a/ts/components/CallScreen.stories.tsx +++ b/ts/components/CallScreen.stories.tsx @@ -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; remoteParticipants?: Array; + 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( 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; export function Default(): JSX.Element { return ; @@ -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 ( { })} /> ); -}; +} -export const _Reconnecting = (): JSX.Element => { +export function Reconnecting(): JSX.Element { return ( { })} /> ); -}; +} -export const _Ended = (): JSX.Element => { +export function Ended(): JSX.Element { return ( { })} /> ); -}; +} 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 ( ({ aci: generateAci(), @@ -347,24 +304,12 @@ export function GroupCallMany(): JSX.Element { ); } -GroupCallMany.story = { - name: 'Group call - Many', -}; - export function GroupCallReconnecting(): JSX.Element { return ( ); } - -GroupCallSomeoneIsSharingScreenAndYoureReconnecting.story = { - name: "Group call - someone is sharing screen and you're reconnecting", -}; diff --git a/ts/components/CallingAudioIndicator.stories.tsx b/ts/components/CallingAudioIndicator.stories.tsx index ae0f02ba7a..5090a4eaf3 100644 --- a/ts/components/CallingAudioIndicator.stories.tsx +++ b/ts/components/CallingAudioIndicator.stories.tsx @@ -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; -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 ( ); } -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 ( diff --git a/ts/components/CallingAudioIndicator.tsx b/ts/components/CallingAudioIndicator.tsx index 4a0eb5b193..f3d1266d67 100644 --- a/ts/components/CallingAudioIndicator.tsx +++ b/ts/components/CallingAudioIndicator.tsx @@ -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 (

= {}): 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; -export function KitchenSink(): JSX.Element { +export function KitchenSink(args: PropsType): JSX.Element { return ( <> - {Object.keys(CallingButtonType).map(buttonType => ( - + {Object.values(CallingButtonType).map(buttonType => ( + ))} ); } -export function AudioOn(): JSX.Element { - const props = createProps({ - buttonType: CallingButtonType.AUDIO_ON, - }); - return ; +export function AudioOn(args: PropsType): JSX.Element { + return ; } -export function AudioOff(): JSX.Element { - const props = createProps({ - buttonType: CallingButtonType.AUDIO_OFF, - }); - return ; +export function AudioOff(args: PropsType): JSX.Element { + return ; } -export function AudioDisabled(): JSX.Element { - const props = createProps({ - buttonType: CallingButtonType.AUDIO_DISABLED, - }); - return ; +export function AudioDisabled(args: PropsType): JSX.Element { + return ( + + ); } -export function VideoOn(): JSX.Element { - const props = createProps({ - buttonType: CallingButtonType.VIDEO_ON, - }); - return ; +export function VideoOn(args: PropsType): JSX.Element { + return ; } -export function VideoOff(): JSX.Element { - const props = createProps({ - buttonType: CallingButtonType.VIDEO_OFF, - }); - return ; +export function VideoOff(args: PropsType): JSX.Element { + return ; } -export function VideoDisabled(): JSX.Element { - const props = createProps({ - buttonType: CallingButtonType.VIDEO_DISABLED, - }); - return ; +export function VideoDisabled(args: PropsType): JSX.Element { + return ( + + ); } -export function TooltipRight(): JSX.Element { - const props = createProps({ - tooltipDirection: TooltipPlacement.Right, - }); - return ; +export function TooltipRight(args: PropsType): JSX.Element { + return ; } -TooltipRight.story = { - name: 'Tooltip right', -}; - -export function PresentingOn(): JSX.Element { - const props = createProps({ - buttonType: CallingButtonType.PRESENTING_ON, - }); - return ; +export function PresentingOn(args: PropsType): JSX.Element { + return ( + + ); } -export function PresentingOff(): JSX.Element { - const props = createProps({ - buttonType: CallingButtonType.PRESENTING_OFF, - }); - return ; +export function PresentingOff(args: PropsType): JSX.Element { + return ( + + ); } diff --git a/ts/components/CallingDeviceSelection.stories.tsx b/ts/components/CallingDeviceSelection.stories.tsx index 0c2935c389..c75828e1c6 100644 --- a/ts/components/CallingDeviceSelection.stories.tsx +++ b/ts/components/CallingDeviceSelection.stories.tsx @@ -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; export function Default(): JSX.Element { return ; diff --git a/ts/components/CallingHeader.stories.tsx b/ts/components/CallingHeader.stories.tsx index e00400db2a..45e0275fb2 100644 --- a/ts/components/CallingHeader.stories.tsx +++ b/ts/components/CallingHeader.stories.tsx @@ -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 => ({ - 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; -export function Default(): JSX.Element { - return ; +export function Default(args: PropsType): JSX.Element { + return ; } -export function LobbyStyle(): JSX.Element { +export function LobbyStyle(args: PropsType): JSX.Element { return ( ; +} -export function WithParticipants(): JSX.Element { +export function WithParticipantsShown(args: PropsType): JSX.Element { return ( ); } -export function WithParticipantsShown(): JSX.Element { +export function LongTitle(args: PropsType): JSX.Element { return ( ); } -WithParticipantsShown.story = { - name: 'With Participants (shown)', -}; - -export function LongTitle(): JSX.Element { +export function TitleWithMessage(args: PropsType): JSX.Element { return ( - + ); } - -export function TitleWithMessage(): JSX.Element { - return ( - - ); -} - -TitleWithMessage.story = { - name: 'Title with message', -}; diff --git a/ts/components/CallingLobby.stories.tsx b/ts/components/CallingLobby.stories.tsx index 645218b6d9..adff338301 100644 --- a/ts/components/CallingLobby.stories.tsx +++ b/ts/components/CallingLobby.stories.tsx @@ -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 => { - 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 => { 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 => { }), 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) => export default { title: 'Components/CallingLobby', -}; + argTypes: {}, + args: {}, +} satisfies Meta; export function Default(): JSX.Element { const props = createProps(); @@ -107,10 +97,6 @@ export function NoCameraNoAvatar(): JSX.Element { return ; } -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 ; } -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 ; } -InitiallyMuted.story = { - name: 'Initially muted', -}; - -export function GroupCall0PeekedParticipants(): JSX.Element { +export function GroupCallWithNoPeekedParticipants(): JSX.Element { const props = createProps({ isGroupCall: true, peekedParticipants: [] }); return ; } -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 ; } -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 ; } -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 ; } -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 ; } -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 ; } -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 ; } - -GroupCall0PeekedParticipantsBigGroup.story = { - name: 'Group Call - 0 peeked participants, big group', -}; diff --git a/ts/components/CallingParticipantsList.stories.tsx b/ts/components/CallingParticipantsList.stories.tsx index 6839325233..e048994c87 100644 --- a/ts/components/CallingParticipantsList.stories.tsx +++ b/ts/components/CallingParticipantsList.stories.tsx @@ -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 => ({ export default { title: 'Components/CallingParticipantsList', -}; +} satisfies Meta; export function NoOne(): JSX.Element { const props = createProps(); return ; } -NoOne.story = { - name: 'No one', -}; - export function SoloCall(): JSX.Element { const props = createProps({ participants: [ diff --git a/ts/components/CallingPip.stories.tsx b/ts/components/CallingPip.stories.tsx index 1049e597a0..2cfc22afc1 100644 --- a/ts/components/CallingPip.stories.tsx +++ b/ts/components/CallingPip.stories.tsx @@ -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 => ({ - 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; -export function Default(): JSX.Element { - const props = createProps({}); - return ; +export function Default(args: PropsType): JSX.Element { + return ; } -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 ; +export function ContactWithAvatarAndNoVideo(args: PropsType): JSX.Element { + return ( + + ); } -ContactWithAvatarAndNoVideo.story = { - name: 'Contact (with avatar and no video)', -}; - -export function ContactNoColor(): JSX.Element { - const props = createProps({ - activeCall: { - ...defaultCall, - conversation: { - ...conversation, - color: undefined, - }, - }, - }); - return ; +export function ContactNoColor(args: PropsType): JSX.Element { + return ( + + ); } -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(), - }, - }); - return ; +export function GroupCall(args: PropsType): JSX.Element { + return ( + getDefaultConversation()), + isConversationTooBigToRing: false, + joinState: GroupCallJoinState.Joined, + maxDevices: 5, + deviceCount: 0, + peekedParticipants: [], + remoteParticipants: [], + remoteAudioLevels: new Map(), + }} + /> + ); } diff --git a/ts/components/CallingPreCallInfo.stories.tsx b/ts/components/CallingPreCallInfo.stories.tsx index 4297403e6b..c20e0d8940 100644 --- a/ts/components/CallingPreCallInfo.stories.tsx +++ b/ts/components/CallingPreCallInfo.stories.tsx @@ -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; 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 ( ); } - -GroupConversationCallIsFull.story = { - name: 'Group conversation, call is full', -}; diff --git a/ts/components/CallingPreCallInfo.tsx b/ts/components/CallingPreCallInfo.tsx index 72992f33fd..d0be1edf92 100644 --- a/ts/components/CallingPreCallInfo.tsx +++ b/ts/components/CallingPreCallInfo.tsx @@ -15,7 +15,7 @@ export enum RingMode { IsRinging, } -type PropsType = { +export type PropsType = { conversation: Pick< ConversationType, | 'acceptedMessageRequest' diff --git a/ts/components/CallingScreenSharingController.stories.tsx b/ts/components/CallingScreenSharingController.stories.tsx index 76211b4887..08a1c30430 100644 --- a/ts/components/CallingScreenSharingController.stories.tsx +++ b/ts/components/CallingScreenSharingController.stories.tsx @@ -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 => ({ export default { title: 'Components/CallingScreenSharingController', -}; +} satisfies Meta; export function Controller(): JSX.Element { return ; @@ -37,7 +38,3 @@ export function ReallyLongAppName(): JSX.Element { /> ); } - -ReallyLongAppName.story = { - name: 'Really long app name', -}; diff --git a/ts/components/CallingSelectPresentingSourcesModal.stories.tsx b/ts/components/CallingSelectPresentingSourcesModal.stories.tsx index e48a88539a..ce7332176e 100644 --- a/ts/components/CallingSelectPresentingSourcesModal.stories.tsx +++ b/ts/components/CallingSelectPresentingSourcesModal.stories.tsx @@ -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; export function Modal(): JSX.Element { return ; diff --git a/ts/components/CaptchaDialog.stories.tsx b/ts/components/CaptchaDialog.stories.tsx index b52aefd6be..d9071414db 100644 --- a/ts/components/CaptchaDialog.stories.tsx +++ b/ts/components/CaptchaDialog.stories.tsx @@ -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; + +export function Basic(args: PropsType): JSX.Element { const [isSkipped, setIsSkipped] = useState(false); if (isSkipped) { return ; } - return ( - setIsSkipped(true)} - /> - ); -}; - -_CaptchaDialog.story = { - name: 'CaptchaDialog', -}; + return setIsSkipped(true)} />; +} diff --git a/ts/components/CaptchaDialog.tsx b/ts/components/CaptchaDialog.tsx index 8f864f0a0d..0b9091960d 100644 --- a/ts/components/CaptchaDialog.tsx +++ b/ts/components/CaptchaDialog.tsx @@ -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; diff --git a/ts/components/ChatColorPicker.stories.tsx b/ts/components/ChatColorPicker.stories.tsx index 597caa4e74..c8b534115b 100644 --- a/ts/components/ChatColorPicker.stories.tsx +++ b/ts/components/ChatColorPicker.stories.tsx @@ -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; 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 ; +export function Default(args: PropsType): JSX.Element { + return ; } const CUSTOM_COLORS = { @@ -62,10 +69,10 @@ const CUSTOM_COLORS = { }, }; -export function CustomColors(): JSX.Element { +export function CustomColors(args: PropsType): JSX.Element { return ( ({ export default { title: 'Components/Checkbox', -}; +} satisfies Meta; export function Normal(): JSX.Element { return ; diff --git a/ts/components/CircleCheckbox.stories.tsx b/ts/components/CircleCheckbox.stories.tsx index 0a7260251e..1e8b033b3f 100644 --- a/ts/components/CircleCheckbox.stories.tsx +++ b/ts/components/CircleCheckbox.stories.tsx @@ -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; export function Normal(): JSX.Element { return ; diff --git a/ts/components/ClearingData.stories.tsx b/ts/components/ClearingData.stories.tsx index 3dacf136b6..e6e183f841 100644 --- a/ts/components/ClearingData.stories.tsx +++ b/ts/components/ClearingData.stories.tsx @@ -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; -export const _ClearingData = (): JSX.Element => ( - -); - -_ClearingData.story = { - name: 'Clearing data', -}; +export function Basic(): JSX.Element { + return ; +} diff --git a/ts/components/CompositionArea.stories.tsx b/ts/components/CompositionArea.stories.tsx index 58d414ce28..3dde92c340 100644 --- a/ts/components/CompositionArea.stories.tsx +++ b/ts/components/CompositionArea.stories.tsx @@ -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 =>
{storyFn()}
, - ] as Array>, -}; + ], + 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 => ({ - 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: _ =>
RECORDING
, + renderSmartCompositionRecordingDraft: _ =>
RECORDING DRAFT
, + // Select mode + selectedMessageIds: undefined, + toggleSelectMode: action('toggleSelectMode'), + toggleForwardMessagesModal: action('toggleForwardMessagesModal'), + }, +} satisfies Meta; - // 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: _ =>
RECORDING
, - renderSmartCompositionRecordingDraft: _ =>
RECORDING DRAFT
, - // Select mode - selectedMessageIds: undefined, - toggleSelectMode: action('toggleSelectMode'), - toggleForwardMessagesModal: action('toggleForwardMessagesModal'), -}); - -export function Default(): JSX.Element { - const props = useProps(); - - return ; +export function Default(args: Props): JSX.Element { + const theme = useContext(StorybookThemeContext); + return ; } -export function StartingText(): JSX.Element { - const props = useProps({ - draftText: "here's some starting text", - }); - - return ; -} - -export function StickerButton(): JSX.Element { - const props = useProps({ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - knownPacks: [{} as any], - }); - - return ; -} - -export function MessageRequest(): JSX.Element { - const props = useProps({ - messageRequestsEnabled: true, - }); - - return ; -} - -export function SmsOnlyFetchingUuid(): JSX.Element { - const props = useProps({ - isSMSOnly: true, - isFetchingUUID: true, - }); - - return ; -} - -SmsOnlyFetchingUuid.story = { - name: 'SMS-only fetching UUID', -}; - -export function SmsOnly(): JSX.Element { - const props = useProps({ - isSMSOnly: true, - }); - - return ; -} - -SmsOnly.story = { - name: 'SMS-only', -}; - -export function Attachments(): JSX.Element { - const props = useProps({ - draftAttachments: [ - fakeDraftAttachment({ - contentType: IMAGE_JPEG, - url: landscapeGreenUrl, - }), - ], - }); - - return ; -} - -export function PendingApproval(): JSX.Element { +export function StartingText(args: Props): JSX.Element { + const theme = useContext(StorybookThemeContext); return ( ); } -AnnouncementsOnlyGroup.story = { - name: 'Announcements Only group', -}; - -export function AnnouncementsOnlyGroup(): JSX.Element { +export function StickerButton(args: Props): JSX.Element { + const theme = useContext(StorybookThemeContext); return ( ); } -AnnouncementsOnlyGroup.story = { - name: 'Announcements Only group', -}; +export function MessageRequest(args: Props): JSX.Element { + const theme = useContext(StorybookThemeContext); + return ; +} -export function Quote(): JSX.Element { +export function SmsOnlyFetchingUuid(args: Props): JSX.Element { + const theme = useContext(StorybookThemeContext); + return ; +} + +export function SmsOnly(args: Props): JSX.Element { + const theme = useContext(StorybookThemeContext); + return ; +} + +export function Attachments(args: Props): JSX.Element { + const theme = useContext(StorybookThemeContext); return ( + ); +} + +export function PendingApproval(args: Props): JSX.Element { + const theme = useContext(StorybookThemeContext); + return ; +} + +export function AnnouncementsOnlyGroup(args: Props): JSX.Element { + const theme = useContext(StorybookThemeContext); + return ( + + ); +} + +export function Quote(args: Props): JSX.Element { + const theme = useContext(StorybookThemeContext); + return ( + + ); +} + +export function QuoteWithPayment(args: Props): JSX.Element { + const theme = useContext(StorybookThemeContext); + return ( + ); } -export function QuoteWithPayment(): JSX.Element { +export function NoFormattingMenu(args: Props): JSX.Element { + const theme = useContext(StorybookThemeContext); + return ( + + ); +} + +export function NoFormattingFlag(args: Props): JSX.Element { + const theme = useContext(StorybookThemeContext); + return ( + + ); +} + +export function NoSpoilerFormattingFlag(args: Props): JSX.Element { + const theme = useContext(StorybookThemeContext); return ( - ); -} - -QuoteWithPayment.story = { - name: 'Quote with payment', -}; - -export function NoFormattingMenu(): JSX.Element { - return ; -} - -export function NoFormattingFlag(): JSX.Element { - return ; -} - -export function NoSpoilerFormattingFlag(): JSX.Element { - return ( - ); } diff --git a/ts/components/CompositionInput.stories.tsx b/ts/components/CompositionInput.stories.tsx index e9b57d1dc0..86371085bd 100644 --- a/ts/components/CompositionInput.stories.tsx +++ b/ts/components/CompositionInput.stories.tsx @@ -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; const useProps = (overrideProps: Partial = {}): 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 => ({ 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 => ({ 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), }); diff --git a/ts/components/CompositionRecording.stories.tsx b/ts/components/CompositionRecording.stories.tsx index d537c39447..7619ccb937 100644 --- a/ts/components/CompositionRecording.stories.tsx +++ b/ts/components/CompositionRecording.stories.tsx @@ -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; export function Default(): JSX.Element { const [active, setActive] = useState(false); diff --git a/ts/components/CompositionRecordingDraft.stories.tsx b/ts/components/CompositionRecordingDraft.stories.tsx index 10d966b5a6..ac59696708 100644 --- a/ts/components/CompositionRecordingDraft.stories.tsx +++ b/ts/components/CompositionRecordingDraft.stories.tsx @@ -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; export function Default(): JSX.Element { const [isPlaying, setIsPlaying] = useState(false); diff --git a/ts/components/CompositionRecordingDraft.tsx b/ts/components/CompositionRecordingDraft.tsx index ae26b10502..de09f672ec 100644 --- a/ts/components/CompositionRecordingDraft.tsx +++ b/ts/components/CompositionRecordingDraft.tsx @@ -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: diff --git a/ts/components/ConfirmDiscardDialog.stories.tsx b/ts/components/ConfirmDiscardDialog.stories.tsx index 9f3bb14976..04ef5da275 100644 --- a/ts/components/ConfirmDiscardDialog.stories.tsx +++ b/ts/components/ConfirmDiscardDialog.stories.tsx @@ -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; export function Default(): JSX.Element { return ; diff --git a/ts/components/ConfirmationDialog.stories.tsx b/ts/components/ConfirmationDialog.stories.tsx index 7a3aec6b5a..629e19e1f4 100644 --- a/ts/components/ConfirmationDialog.stories.tsx +++ b/ts/components/ConfirmationDialog.stories.tsx @@ -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; -export const _ConfirmationDialog = (): JSX.Element => { +export function Basic(): JSX.Element { return ( { asdf blip ); -}; - -_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 ( ; type ContactType = Omit; @@ -54,10 +55,6 @@ export function EmptyList(): JSX.Element { return ; } -EmptyList.story = { - name: 'Empty list', -}; - export function OneContact(): JSX.Element { return ( @@ -66,10 +63,6 @@ export function OneContact(): JSX.Element { ); } -OneContact.story = { - name: 'One contact', -}; - export function ThreeContacts(): JSX.Element { return ( @@ -80,10 +73,6 @@ export function ThreeContacts(): JSX.Element { ); } -ThreeContacts.story = { - name: 'Three contacts', -}; - export function FourContactsOneWithALongName(): JSX.Element { return ( @@ -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 ( @@ -114,7 +99,3 @@ export function FiftyContacts(): JSX.Element { ); } - -FiftyContacts.story = { - name: 'Fifty contacts', -}; diff --git a/ts/components/ContextMenu.stories.tsx b/ts/components/ContextMenu.stories.tsx index 5dc2b03831..cdb4e70f33 100644 --- a/ts/components/ContextMenu.stories.tsx +++ b/ts/components/ContextMenu.stories.tsx @@ -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>; const getDefaultProps = (): PropsType => ({ i18n, diff --git a/ts/components/ConversationList.stories.tsx b/ts/components/ConversationList.stories.tsx index 8e2db3fe43..7422077a71 100644 --- a/ts/components/ConversationList.stories.tsx +++ b/ts/components/ConversationList.stories.tsx @@ -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; const defaultConversations: Array = [ getDefaultConversation({ @@ -105,15 +105,13 @@ function Wrapper({ ); } -export const _ArchiveButton = (): JSX.Element => ( - -); - -_ArchiveButton.story = { - name: 'Archive button', -}; +export function ArchiveButton(): JSX.Element { + return ( + + ); +} 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 ( = {} ): 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 ( 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 ( 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 ; } -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 ; } -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 ( ); } - -KitchenSink.story = { - name: 'Kitchen sink', -}; diff --git a/ts/components/CrashReportDialog.stories.tsx b/ts/components/CrashReportDialog.stories.tsx index 3c5c942b3d..dc45a2dde1 100644 --- a/ts/components/CrashReportDialog.stories.tsx +++ b/ts/components/CrashReportDialog.stories.tsx @@ -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; 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', -}; +} diff --git a/ts/components/CrashReportDialog.tsx b/ts/components/CrashReportDialog.tsx index 28e73d56e2..4f20b62c3c 100644 --- a/ts/components/CrashReportDialog.tsx +++ b/ts/components/CrashReportDialog.tsx @@ -13,7 +13,7 @@ type PropsActionsType = { eraseCrashReports: () => void; }; -type PropsType = { +export type PropsType = { i18n: LocalizerType; isPending: boolean; } & PropsActionsType; diff --git a/ts/components/CustomColorEditor.stories.tsx b/ts/components/CustomColorEditor.stories.tsx index 173e7cc454..91beaaee45 100644 --- a/ts/components/CustomColorEditor.stories.tsx +++ b/ts/components/CustomColorEditor.stories.tsx @@ -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; const i18n = setupI18n('en', enMessages); diff --git a/ts/components/CustomizingPreferredReactionsModal.stories.tsx b/ts/components/CustomizingPreferredReactionsModal.stories.tsx index 62a69bc677..443b430bd1 100644 --- a/ts/components/CustomizingPreferredReactionsModal.stories.tsx +++ b/ts/components/CustomizingPreferredReactionsModal.stories.tsx @@ -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; const defaultProps: ComponentProps = { @@ -50,10 +52,6 @@ export function DraftEmojiSelected(): JSX.Element { ); } -DraftEmojiSelected.story = { - name: 'Draft emoji selected', -}; - export function Saving(): JSX.Element { return ; } @@ -61,7 +59,3 @@ export function Saving(): JSX.Element { export function HadError(): JSX.Element { return ; } - -HadError.story = { - name: 'Had error', -}; diff --git a/ts/components/CustomizingPreferredReactionsModal.tsx b/ts/components/CustomizingPreferredReactionsModal.tsx index ae50f9511b..073cc06ac6 100644 --- a/ts/components/CustomizingPreferredReactionsModal.tsx +++ b/ts/components/CustomizingPreferredReactionsModal.tsx @@ -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; hadSaveError: boolean; i18n: LocalizerType; diff --git a/ts/components/DebugLogWindow.stories.tsx b/ts/components/DebugLogWindow.stories.tsx index 7118a5bbe8..2e226a0db0 100644 --- a/ts/components/DebugLogWindow.stories.tsx +++ b/ts/components/DebugLogWindow.stories.tsx @@ -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; -export const _DebugLogWindow = (): JSX.Element => ( - -); - -_DebugLogWindow.story = { - name: 'DebugLogWindow', -}; +export function Basic(): JSX.Element { + return ; +} diff --git a/ts/components/DialogExpiredBuild.stories.tsx b/ts/components/DialogExpiredBuild.stories.tsx index 11df133f0a..2d8b732e63 100644 --- a/ts/components/DialogExpiredBuild.stories.tsx +++ b/ts/components/DialogExpiredBuild.stories.tsx @@ -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; -export const _DialogExpiredBuild = (): JSX.Element => { - const containerWidthBreakpoint = select( - 'containerWidthBreakpoint', - WidthBreakpoint, - WidthBreakpoint.Wide - ); +export function Basic(): JSX.Element { + const containerWidthBreakpoint = WidthBreakpoint.Wide; return ( @@ -31,8 +29,4 @@ export const _DialogExpiredBuild = (): JSX.Element => { /> ); -}; - -_DialogExpiredBuild.story = { - name: 'DialogExpiredBuild', -}; +} diff --git a/ts/components/DialogNetworkStatus.stories.tsx b/ts/components/DialogNetworkStatus.stories.tsx index 9bcf717e1a..cfbec3cc14 100644 --- a/ts/components/DialogNetworkStatus.stories.tsx +++ b/ts/components/DialogNetworkStatus.stories.tsx @@ -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; 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 ( @@ -84,10 +81,6 @@ export function ClosingWide(): JSX.Element { ); } -ClosingWide.story = { - name: 'Closing Wide', -}; - export function ClosedWide(): JSX.Element { return ( @@ -100,10 +93,6 @@ export function ClosedWide(): JSX.Element { ); } -ClosedWide.story = { - name: 'Closed Wide', -}; - export function OfflineWide(): JSX.Element { return ( @@ -116,10 +105,6 @@ export function OfflineWide(): JSX.Element { ); } -OfflineWide.story = { - name: 'Offline Wide', -}; - export function ConnectingNarrow(): JSX.Element { return ( @@ -132,10 +117,6 @@ export function ConnectingNarrow(): JSX.Element { ); } -ConnectingNarrow.story = { - name: 'Connecting Narrow', -}; - export function ClosingNarrow(): JSX.Element { return ( @@ -148,10 +129,6 @@ export function ClosingNarrow(): JSX.Element { ); } -ClosingNarrow.story = { - name: 'Closing Narrow', -}; - export function ClosedNarrow(): JSX.Element { return ( @@ -164,10 +141,6 @@ export function ClosedNarrow(): JSX.Element { ); } -ClosedNarrow.story = { - name: 'Closed Narrow', -}; - export function OfflineNarrow(): JSX.Element { return ( @@ -179,7 +152,3 @@ export function OfflineNarrow(): JSX.Element { ); } - -OfflineNarrow.story = { - name: 'Offline Narrow', -}; diff --git a/ts/components/DialogRelink.stories.tsx b/ts/components/DialogRelink.stories.tsx index 813da6e456..01ab3e7fcb 100644 --- a/ts/components/DialogRelink.stories.tsx +++ b/ts/components/DialogRelink.stories.tsx @@ -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; export function Iterations(): JSX.Element { return ( diff --git a/ts/components/DialogUpdate.stories.tsx b/ts/components/DialogUpdate.stories.tsx index 4e202cd5f9..097206d812 100644 --- a/ts/components/DialogUpdate.stories.tsx +++ b/ts/components/DialogUpdate.stories.tsx @@ -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; 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 ( @@ -64,10 +63,6 @@ export function UpdateWide(): JSX.Element { ); } -UpdateWide.story = { - name: 'Update (Wide)', -}; - export function DownloadedWide(): JSX.Element { return ( @@ -81,10 +76,6 @@ export function DownloadedWide(): JSX.Element { ); } -DownloadedWide.story = { - name: 'Downloaded (Wide)', -}; - export function DownloadReadyWide(): JSX.Element { return ( @@ -99,10 +90,6 @@ export function DownloadReadyWide(): JSX.Element { ); } -DownloadReadyWide.story = { - name: 'DownloadReady (Wide)', -}; - export function FullDownloadReadyWide(): JSX.Element { return ( @@ -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 ( @@ -170,10 +149,6 @@ export function CannotUpdateWide(): JSX.Element { ); } -CannotUpdateWide.story = { - name: 'Cannot_Update (Wide)', -}; - export function CannotUpdateBetaWide(): JSX.Element { return ( @@ -187,10 +162,6 @@ export function CannotUpdateBetaWide(): JSX.Element { ); } -CannotUpdateBetaWide.story = { - name: 'Cannot_Update_Beta (Wide)', -}; - export function CannotUpdateRequireManualWide(): JSX.Element { return ( @@ -204,10 +175,6 @@ export function CannotUpdateRequireManualWide(): JSX.Element { ); } -CannotUpdateRequireManualWide.story = { - name: 'Cannot_Update_Require_Manual (Wide)', -}; - export function CannotUpdateRequireManualBetaWide(): JSX.Element { return ( @@ -221,10 +188,6 @@ export function CannotUpdateRequireManualBetaWide(): JSX.Element { ); } -CannotUpdateRequireManualBetaWide.story = { - name: 'Cannot_Update_Require_Manual_Beta (Wide)', -}; - export function MacOSReadOnlyWide(): JSX.Element { return ( @@ -238,10 +201,6 @@ export function MacOSReadOnlyWide(): JSX.Element { ); } -MacOSReadOnlyWide.story = { - name: 'MacOS_Read_Only (Wide)', -}; - export function UnsupportedOSWide(): JSX.Element { return ( @@ -255,10 +214,6 @@ export function UnsupportedOSWide(): JSX.Element { ); } -UnsupportedOSWide.story = { - name: 'UnsupportedOS (Wide)', -}; - export function UpdateNarrow(): JSX.Element { return ( @@ -272,10 +227,6 @@ export function UpdateNarrow(): JSX.Element { ); } -UpdateNarrow.story = { - name: 'Update (Narrow)', -}; - export function DownloadedNarrow(): JSX.Element { return ( @@ -289,10 +240,6 @@ export function DownloadedNarrow(): JSX.Element { ); } -DownloadedNarrow.story = { - name: 'Downloaded (Narrow)', -}; - export function DownloadReadyNarrow(): JSX.Element { return ( @@ -307,10 +254,6 @@ export function DownloadReadyNarrow(): JSX.Element { ); } -DownloadReadyNarrow.story = { - name: 'DownloadReady (Narrow)', -}; - export function FullDownloadReadyNarrow(): JSX.Element { return ( @@ -325,10 +268,6 @@ export function FullDownloadReadyNarrow(): JSX.Element { ); } -FullDownloadReadyNarrow.story = { - name: 'FullDownloadReady (Narrow)', -}; - export function DownloadingNarrow(): JSX.Element { return ( @@ -342,10 +281,6 @@ export function DownloadingNarrow(): JSX.Element { ); } -DownloadingNarrow.story = { - name: 'Downloading (Narrow)', -}; - export function CannotUpdateNarrow(): JSX.Element { return ( @@ -359,10 +294,6 @@ export function CannotUpdateNarrow(): JSX.Element { ); } -CannotUpdateNarrow.story = { - name: 'Cannot Update (Narrow)', -}; - export function CannotUpdateBetaNarrow(): JSX.Element { return ( @@ -376,10 +307,6 @@ export function CannotUpdateBetaNarrow(): JSX.Element { ); } -CannotUpdateBetaNarrow.story = { - name: 'Cannot Update Beta (Narrow)', -}; - export function CannotUpdateRequireManualNarrow(): JSX.Element { return ( @@ -393,10 +320,6 @@ export function CannotUpdateRequireManualNarrow(): JSX.Element { ); } -CannotUpdateRequireManualNarrow.story = { - name: 'Cannot_Update_Require_Manual (Narrow)', -}; - export function CannotUpdateRequireManualBetaNarrow(): JSX.Element { return ( @@ -410,10 +333,6 @@ export function CannotUpdateRequireManualBetaNarrow(): JSX.Element { ); } -CannotUpdateRequireManualBetaNarrow.story = { - name: 'Cannot_Update_Require_Manual_Beta (Narrow)', -}; - export function MacOSReadOnlyNarrow(): JSX.Element { return ( @@ -427,10 +346,6 @@ export function MacOSReadOnlyNarrow(): JSX.Element { ); } -MacOSReadOnlyNarrow.story = { - name: 'MacOS_Read_Only (Narrow)', -}; - export function UnsupportedOSNarrow(): JSX.Element { return ( @@ -443,7 +358,3 @@ export function UnsupportedOSNarrow(): JSX.Element { ); } - -UnsupportedOSNarrow.story = { - name: 'UnsupportedOS (Narrow)', -}; diff --git a/ts/components/DisappearingTimeDialog.stories.tsx b/ts/components/DisappearingTimeDialog.stories.tsx index 3ed55aa89a..69a99e1157 100644 --- a/ts/components/DisappearingTimeDialog.stories.tsx +++ b/ts/components/DisappearingTimeDialog.stories.tsx @@ -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; const i18n = setupI18n('en', enMessages); diff --git a/ts/components/DisappearingTimerSelect.stories.tsx b/ts/components/DisappearingTimerSelect.stories.tsx index 11d8973f0c..460abf1d6f 100644 --- a/ts/components/DisappearingTimerSelect.stories.tsx +++ b/ts/components/DisappearingTimerSelect.stories.tsx @@ -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; 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 ; } -InitialValue1Day.story = { - name: 'Initial value: 1 day', -}; - export function InitialValue3DaysCustomTime(): JSX.Element { return ; } - -InitialValue3DaysCustomTime.story = { - name: 'Initial value 3 days (Custom time)', -}; diff --git a/ts/components/EditUsernameModalBody.stories.tsx b/ts/components/EditUsernameModalBody.stories.tsx index 1e9fed1539..92a9db4885 100644 --- a/ts/components/EditUsernameModalBody.stories.tsx +++ b/ts/components/EditUsernameModalBody.stories.tsx @@ -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; type ArgsType = PropsType & { discriminator?: string; @@ -81,7 +77,7 @@ type ArgsType = PropsType & { }; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => { +const Template: StoryFn = args => { let { reservation } = args; if (!reservation && args.discriminator) { reservation = { @@ -95,27 +91,16 @@ const Template: Story = 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', -}; diff --git a/ts/components/ErrorModal.stories.tsx b/ts/components/ErrorModal.stories.tsx index 51a3d40b7a..20775c3187 100644 --- a/ts/components/ErrorModal.stories.tsx +++ b/ts/components/ErrorModal.stories.tsx @@ -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 => ({ - 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; export function Normal(): JSX.Element { return ; diff --git a/ts/components/ForwardMessagesModal.stories.tsx b/ts/components/ForwardMessagesModal.stories.tsx index 0cf043ed2d..a078f4a4ae 100644 --- a/ts/components/ForwardMessagesModal.stories.tsx +++ b/ts/components/ForwardMessagesModal.stories.tsx @@ -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 => ({ 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; 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 ( ); } - -AnnouncementOnlyGroupsNonAdmin.story = { - name: 'announcement only groups non-admin', -}; diff --git a/ts/components/ForwardMessagesModal.tsx b/ts/components/ForwardMessagesModal.tsx index 2675014b56..7ea463566f 100644 --- a/ts/components/ForwardMessagesModal.tsx +++ b/ts/components/ForwardMessagesModal.tsx @@ -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; diff --git a/ts/components/GroupCallOverflowArea.stories.tsx b/ts/components/GroupCallOverflowArea.stories.tsx index 1bd1272cd5..17cf8eb16f 100644 --- a/ts/components/GroupCallOverflowArea.stories.tsx +++ b/ts/components/GroupCallOverflowArea.stories.tsx @@ -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; 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 ( @@ -83,10 +81,6 @@ export function OneOverflowedParticipant(): JSX.Element { ); } -OneOverflowedParticipant.story = { - name: 'One overflowed participant', -}; - export function ThreeOverflowedParticipants(): JSX.Element { return ( @@ -98,10 +92,6 @@ export function ThreeOverflowedParticipants(): JSX.Element { ); } -ThreeOverflowedParticipants.story = { - name: 'Three overflowed participants', -}; - export function ManyOverflowedParticipants(): JSX.Element { return ( @@ -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 )} /> ); } - -ManyOverflowedParticipants.story = { - name: 'Many overflowed participants', -}; diff --git a/ts/components/GroupCallOverflowArea.tsx b/ts/components/GroupCallOverflowArea.tsx index b60a135e0b..76edcebe67 100644 --- a/ts/components/GroupCallOverflowArea.tsx +++ b/ts/components/GroupCallOverflowArea.tsx @@ -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; diff --git a/ts/components/GroupCallRemoteParticipant.stories.tsx b/ts/components/GroupCallRemoteParticipant.stories.tsx index 5d7cc94ef7..3eb13477a8 100644 --- a/ts/components/GroupCallRemoteParticipant.stories.tsx +++ b/ts/components/GroupCallRemoteParticipant.stories.tsx @@ -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; 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 ( ; function Wrapper({ disabled, diff --git a/ts/components/GroupDescriptionInput.tsx b/ts/components/GroupDescriptionInput.tsx index 0534a8d101..2a470d819c 100644 --- a/ts/components/GroupDescriptionInput.tsx +++ b/ts/components/GroupDescriptionInput.tsx @@ -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; diff --git a/ts/components/GroupTitleInput.stories.tsx b/ts/components/GroupTitleInput.stories.tsx index af128f3506..d873de91de 100644 --- a/ts/components/GroupTitleInput.stories.tsx +++ b/ts/components/GroupTitleInput.stories.tsx @@ -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; function Wrapper({ disabled, diff --git a/ts/components/GroupTitleInput.tsx b/ts/components/GroupTitleInput.tsx index 23def66a20..ebd75e6c68 100644 --- a/ts/components/GroupTitleInput.tsx +++ b/ts/components/GroupTitleInput.tsx @@ -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; diff --git a/ts/components/GroupV1MigrationDialog.stories.tsx b/ts/components/GroupV1MigrationDialog.stories.tsx index e86725e4e1..a6464eb5d9 100644 --- a/ts/components/GroupV1MigrationDialog.stories.tsx +++ b/ts/components/GroupV1MigrationDialog.stories.tsx @@ -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 => ({ export default { title: 'Components/GroupV1MigrationDialog', -}; +} satisfies Meta; export function NotYetMigratedBasic(): JSX.Element { return ; } -NotYetMigratedBasic.story = { - name: 'Not yet migrated, basic', -}; - export function MigratedBasic(): JSX.Element { return ( ); } - -NotYetMigratedJustDroppedMember.story = { - name: 'Not yet migrated, just dropped member', -}; diff --git a/ts/components/GroupV2JoinDialog.stories.tsx b/ts/components/GroupV2JoinDialog.stories.tsx index ab05156166..6a348fad4f 100644 --- a/ts/components/GroupV2JoinDialog.stories.tsx +++ b/ts/components/GroupV2JoinDialog.stories.tsx @@ -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 => ({ - 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 => ({ export default { title: 'Components/GroupV2JoinDialog', -}; + argTypes: {}, + args: {}, +} satisfies Meta; export function Basic(): JSX.Element { return ; @@ -45,10 +43,6 @@ export function ApprovalRequired(): JSX.Element { ); } -ApprovalRequired.story = { - name: 'Approval required', -}; - export function WithAvatar(): JSX.Element { return ( = []; diff --git a/ts/components/InContactsIcon.stories.tsx b/ts/components/InContactsIcon.stories.tsx index 5c5acb6323..42db8da1b2 100644 --- a/ts/components/InContactsIcon.stories.tsx +++ b/ts/components/InContactsIcon.stories.tsx @@ -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; export function Default(): JSX.Element { return ; diff --git a/ts/components/InContactsIcon.tsx b/ts/components/InContactsIcon.tsx index 9ee209a45c..e0cfe0d218 100644 --- a/ts/components/InContactsIcon.tsx +++ b/ts/components/InContactsIcon.tsx @@ -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; i18n: LocalizerType; diff --git a/ts/components/Inbox.stories.tsx b/ts/components/Inbox.stories.tsx index 2131504383..b2f93e4dfc 100644 --- a/ts/components/Inbox.stories.tsx +++ b/ts/components/Inbox.stories.tsx @@ -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; // eslint-disable-next-line react/function-component-definition -const Template: Story = ({ +const Template: StoryFn = ({ daysAgo, ...args }) => { @@ -90,6 +64,3 @@ const Template: Story = ({ }; export const Default = Template.bind({}); -Default.story = { - name: 'Default', -}; diff --git a/ts/components/IncomingCallBar.stories.tsx b/ts/components/IncomingCallBar.stories.tsx index 0228afebca..099e55c19c 100644 --- a/ts/components/IncomingCallBar.stories.tsx +++ b/ts/components/IncomingCallBar.stories.tsx @@ -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 './IncomingCallBar'; import { IncomingCallBar } from './IncomingCallBar'; import { CallMode } from '../types/Calling'; import { setupI18n } from '../util/setupI18n'; @@ -53,7 +54,7 @@ const groupConversation = getDefaultConversation({ export default { title: 'Components/IncomingCallBar', -}; +} satisfies Meta; export function IncomingDirectCallVideo(): JSX.Element { return ( @@ -66,10 +67,6 @@ export function IncomingDirectCallVideo(): JSX.Element { ); } -IncomingDirectCallVideo.story = { - name: 'Incoming direct call (video)', -}; - export function IncomingDirectCallAudio(): JSX.Element { return ( ); } - -IncomingGroupCallCallingYouAnd4Others.story = { - name: 'Incoming group call (calling you and 4 others)', -}; diff --git a/ts/components/Input.stories.tsx b/ts/components/Input.stories.tsx index 370358fa57..e6b580e901 100644 --- a/ts/components/Input.stories.tsx +++ b/ts/components/Input.stories.tsx @@ -2,10 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import React, { useState } from 'react'; - -import { text } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import type { PropsType } from './Input'; import { Input } from './Input'; import { setupI18n } from '../util/setupI18n'; @@ -15,7 +13,9 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Input', -}; + argTypes: {}, + args: {}, +} satisfies Meta; const createProps = (overrideProps: Partial = {}): PropsType => ({ disabled: Boolean(overrideProps.disabled), @@ -26,11 +26,8 @@ const createProps = (overrideProps: Partial = {}): PropsType => ({ icon: overrideProps.icon, maxLengthCount: overrideProps.maxLengthCount, onChange: action('onChange'), - placeholder: text( - 'placeholder', - overrideProps.placeholder || 'Enter some text here' - ), - value: text('value', overrideProps.value || ''), + placeholder: overrideProps.placeholder ?? 'Enter some text here', + value: overrideProps.value ?? '', whenToShowRemainingCount: overrideProps.whenToShowRemainingCount, }); @@ -55,10 +52,6 @@ export function HasClearButton(): JSX.Element { ); } -HasClearButton.story = { - name: 'hasClearButton', -}; - export function CharacterCount(): JSX.Element { return ( ); } - -SpellcheckDisabled.story = { - name: 'spellcheck disabled', -}; diff --git a/ts/components/Intl.stories.tsx b/ts/components/Intl.stories.tsx index d2dfb2359b..1460c902dd 100644 --- a/ts/components/Intl.stories.tsx +++ b/ts/components/Intl.stories.tsx @@ -1,7 +1,7 @@ // 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 type { Props } from './Intl'; @@ -14,7 +14,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Intl', component: Intl, -} as Meta; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ i18n, @@ -24,7 +24,7 @@ const createProps = (overrideProps: Partial = {}): Props => ({ // eslint-disable-next-line max-len // eslint-disable-next-line react/function-component-definition, local-rules/valid-i18n-keys -const Template: Story = args => ; +const Template: StoryFn = args => ; export const NoReplacements = Template.bind({}); NoReplacements.args = createProps({ diff --git a/ts/components/LeftPane.stories.tsx b/ts/components/LeftPane.stories.tsx index 3b95675eef..4c27e6c69f 100644 --- a/ts/components/LeftPane.stories.tsx +++ b/ts/components/LeftPane.stories.tsx @@ -2,10 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - import { action } from '@storybook/addon-actions'; -import { boolean, select } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import type { PropsType } from './LeftPane'; import { LeftPane, LeftPaneMode } from './LeftPane'; import { CaptchaDialog } from './CaptchaDialog'; @@ -45,7 +43,9 @@ type OverridePropsType = Partial & { export default { title: 'Components/LeftPane', -}; + argTypes: {}, + args: {}, +} satisfies Meta; const defaultConversations: Array = [ getDefaultConversation({ @@ -126,11 +126,8 @@ const useProps = (overrideProps: OverridePropsType = {}): PropsType => { }; } - const isUpdateDownloaded = boolean('isUpdateDownloaded', false); - const isContactManagementEnabled = boolean( - 'isContactManagementEnabled', - true - ); + const isUpdateDownloaded = false; + const isContactManagementEnabled = true; return { otherTabsUnreadStats: { @@ -151,28 +148,20 @@ const useProps = (overrideProps: OverridePropsType = {}): PropsType => { hasFailedStorySends: false, hasPendingUpdate: false, i18n, - isMacOS: boolean('isMacOS', false), + isMacOS: false, preferredWidthFromStorage: 320, regionCode: 'US', - challengeStatus: select( - 'challengeStatus', - ['idle', 'required', 'pending'], - 'idle' - ), - crashReportCount: select('challengeReportCount', [0, 1], 0), + challengeStatus: 'idle', + crashReportCount: 0, - hasNetworkDialog: boolean('hasNetworkDialog', false), - hasExpiredDialog: boolean('hasExpiredDialog', false), - hasRelinkDialog: boolean('hasRelinkDialog', false), - hasUpdateDialog: boolean('hasUpdateDialog', false), - unsupportedOSDialogType: select( - 'unsupportedOSDialogType', - ['error', 'warning', undefined], - undefined - ), + hasNetworkDialog: false, + hasExpiredDialog: false, + hasRelinkDialog: false, + hasUpdateDialog: false, + unsupportedOSDialogType: undefined, isUpdateDownloaded, isContactManagementEnabled, - navTabsCollapsed: boolean('navTabsCollapsed', false), + navTabsCollapsed: false, setChallengeStatus: action('setChallengeStatus'), lookupConversationWithoutServiceId: @@ -315,10 +304,6 @@ export function InboxNoConversations(): JSX.Element { ); } -InboxNoConversations.story = { - name: 'Inbox: no conversations', -}; - export function InboxOnlyPinnedConversations(): JSX.Element { return ( ; } -InboxPinnedNonPinnedAndArchivedConversations.story = { - name: 'Inbox: pinned, non-pinned, and archived conversations', -}; - export function SearchNoResultsWhenSearchingEverywhere(): JSX.Element { return ( ( - -); - -_CrashReportDialog.story = { - name: 'Crash report dialog', -}; +export function _CrashReportDialog(): JSX.Element { + return ( + + ); +} export function ChooseGroupMembersPartialPhoneNumber(): JSX.Element { return ( @@ -1055,10 +910,6 @@ export function ChooseGroupMembersPartialPhoneNumber(): JSX.Element { ); } -ChooseGroupMembersPartialPhoneNumber.story = { - name: 'Choose Group Members: Partial phone number', -}; - export function ChooseGroupMembersValidPhoneNumber(): JSX.Element { return ( null, clickLabel: 'Click me', containerWidthBreakpoint: WidthBreakpoint.Wide, }, -}; +} satisfies Meta; export const Update = { args: { diff --git a/ts/components/Lightbox.stories.tsx b/ts/components/Lightbox.stories.tsx index 5f600473ec..a6c55eb72c 100644 --- a/ts/components/Lightbox.stories.tsx +++ b/ts/components/Lightbox.stories.tsx @@ -2,11 +2,9 @@ // SPDX-License-Identifier: AGPL-3.0-only import React, { useState } from 'react'; - import { action } from '@storybook/addon-actions'; -import { number } from '@storybook/addon-knobs'; import { noop } from 'lodash'; - +import type { Meta } from '@storybook/react'; import enMessages from '../../_locales/en/messages.json'; import type { PropsType } from './Lightbox'; import { Lightbox } from './Lightbox'; @@ -26,7 +24,9 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Lightbox', -}; + argTypes: {}, + args: {}, +} satisfies Meta; type OverridePropsMediaItemType = Partial & { caption?: string }; @@ -57,9 +57,7 @@ function createMediaItem( const createProps = (overrideProps: Partial = {}): PropsType => { // eslint-disable-next-line react-hooks/rules-of-hooks - const [selectedIndex, setSelectedIndex] = useState( - number('selectedIndex', overrideProps.selectedIndex || 0) - ); + const [selectedIndex, setSelectedIndex] = useState(0); const media = overrideProps.media || []; return { closeLightbox: action('closeLightbox'), @@ -196,10 +194,6 @@ export function ImageWithCaptionNormalImage(): JSX.Element { ); } -ImageWithCaptionNormalImage.story = { - name: 'Image with Caption (normal image)', -}; - export function ImageWithCaptionAllWhiteImage(): JSX.Element { return ( { + log.error('Lightbox: Failed to play video', Errors.toLogFormat(error)); + }); } else { videoElement.pause(); } diff --git a/ts/components/ListTile.stories.tsx b/ts/components/ListTile.stories.tsx index 279c8cbf13..b54c376b7b 100644 --- a/ts/components/ListTile.stories.tsx +++ b/ts/components/ListTile.stories.tsx @@ -1,7 +1,7 @@ // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Story } from '@storybook/react'; +import type { Meta, StoryFn } from '@storybook/react'; import React from 'react'; import { ListTile } from './ListTile'; import type { Props } from './ListTile'; @@ -11,12 +11,12 @@ import { UserText } from './UserText'; export default { title: 'Components/ListTile', component: ListTile, -}; +} satisfies Meta; const lorem = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam feugiat quam vitae semper facilisis. Praesent eu efficitur dui. Donec semper mattis nisl non hendrerit.'; -function TemplateList(width: number): Story { +function TemplateList(width: number): StoryFn { // eslint-disable-next-line react/display-name return args => { return ( diff --git a/ts/components/MediaEditor.stories.tsx b/ts/components/MediaEditor.stories.tsx index 53ec34c3f0..7ac7928a52 100644 --- a/ts/components/MediaEditor.stories.tsx +++ b/ts/components/MediaEditor.stories.tsx @@ -1,9 +1,10 @@ // Copyright 2021 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 React from 'react'; +import { action } from '@storybook/addon-actions'; import type { PropsType } from './MediaEditor'; import { MediaEditor } from './MediaEditor'; import enMessages from '../../_locales/en/messages.json'; @@ -19,48 +20,28 @@ const IMAGE_4 = '/fixtures/snow.jpg'; export default { title: 'Components/MediaEditor', component: MediaEditor, - argTypes: { - getPreferredBadge: { action: true }, - i18n: { - defaultValue: i18n, - }, - imageToBlurHash: { action: true }, - imageSrc: { - defaultValue: IMAGE_2, - }, - installedPacks: { - defaultValue: installedPacks, - }, - isFormattingEnabled: { - defaultValue: true, - }, - isFormattingFlagEnabled: { - defaultValue: true, - }, - isFormattingSpoilersFlagEnabled: { - defaultValue: true, - }, - isSending: { - defaultValue: false, - }, - onClose: { action: true }, - onDone: { action: true }, - onPickEmoji: { action: true }, - onTextTooLong: { action: true }, - platform: { - defaultValue: 'darwin', - }, - recentStickers: { - defaultValue: [Stickers.wide, Stickers.tall, Stickers.abe], - }, - skinTone: { - defaultValue: 0, - }, + args: { + getPreferredBadge: () => undefined, + i18n, + imageToBlurHash: input => Promise.resolve(input.toString()), + imageSrc: IMAGE_2, + installedPacks, + isFormattingEnabled: true, + isFormattingFlagEnabled: true, + isFormattingSpoilersFlagEnabled: true, + isSending: false, + onClose: action('onClose'), + onDone: action('onDone'), + onPickEmoji: action('onPickEmoji'), + onTextTooLong: action('onTextTooLong'), + platform: 'darwin', + recentStickers: [Stickers.wide, Stickers.tall, Stickers.abe], + skinTone: 0, }, -} as Meta; +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; +const Template: StoryFn = args => ; export const ExtraLarge = Template.bind({}); diff --git a/ts/components/MediaQualitySelector.stories.tsx b/ts/components/MediaQualitySelector.stories.tsx index a1277f3516..61ba742e1e 100644 --- a/ts/components/MediaQualitySelector.stories.tsx +++ b/ts/components/MediaQualitySelector.stories.tsx @@ -2,10 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import React from 'react'; - import { action } from '@storybook/addon-actions'; -import { boolean } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import enMessages from '../../_locales/en/messages.json'; import type { PropsType } from './MediaQualitySelector'; import { MediaQualitySelector } from './MediaQualitySelector'; @@ -13,14 +11,16 @@ import { setupI18n } from '../util/setupI18n'; export default { title: 'Components/MediaQualitySelector', -}; + argTypes: {}, + args: {}, +} satisfies Meta; const i18n = setupI18n('en', enMessages); const createProps = (overrideProps: Partial = {}): PropsType => ({ conversationId: 'abc123', i18n, - isHighQuality: boolean('isHighQuality', Boolean(overrideProps.isHighQuality)), + isHighQuality: overrideProps.isHighQuality ?? false, onSelectQuality: action('onSelectQuality'), }); diff --git a/ts/components/MiniPlayer.stories.tsx b/ts/components/MiniPlayer.stories.tsx index 7c5e58cc89..f1570e3b1d 100644 --- a/ts/components/MiniPlayer.stories.tsx +++ b/ts/components/MiniPlayer.stories.tsx @@ -2,6 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import React, { useEffect, useState } from 'react'; +import type { Meta } from '@storybook/react'; +import type { Props } from './MiniPlayer'; import { MiniPlayer, PlayerState } from './MiniPlayer'; import { setupI18n } from '../util/setupI18n'; @@ -15,7 +17,7 @@ audio.src = '/fixtures/incompetech-com-Agnus-Dei-X.mp3'; export default { title: 'components/MiniPlayer', component: MiniPlayer, -}; +} satisfies Meta; export function Default(): JSX.Element { const [active, setActive] = useState(false); diff --git a/ts/components/Modal.stories.tsx b/ts/components/Modal.stories.tsx index 267fdffeb5..6a4034febc 100644 --- a/ts/components/Modal.stories.tsx +++ b/ts/components/Modal.stories.tsx @@ -3,19 +3,19 @@ import React from 'react'; import { noop } 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 { Button } from './Button'; +import type { ModalPropsType } from './Modal'; import { Modal } from './Modal'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/Modal', -}; +} satisfies Meta; const onClose = action('onClose'); @@ -30,10 +30,6 @@ export function BareBonesShort(): JSX.Element { ); } -BareBonesShort.story = { - name: 'Bare bones, short', -}; - export function BareBonesLong(): JSX.Element { return ( @@ -45,10 +41,6 @@ export function BareBonesLong(): JSX.Element { ); } -BareBonesLong.story = { - name: 'Bare bones, long', -}; - export function BareBonesLongWithButton(): JSX.Element { return ( ); } - -WithBackButton.story = { - name: 'Back Button', -}; diff --git a/ts/components/MyStories.stories.tsx b/ts/components/MyStories.stories.tsx index 8d916eff86..915fae0631 100644 --- a/ts/components/MyStories.stories.tsx +++ b/ts/components/MyStories.stories.tsx @@ -1,13 +1,14 @@ // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Meta, ReactFramework, Story } from '@storybook/react'; +import type { Meta, ReactRenderer, StoryFn } from '@storybook/react'; import type { PlayFunction } from '@storybook/csf'; import React from 'react'; -import { expect } from '@storybook/jest'; +import { expect, jest } from '@storybook/jest'; import { v4 as uuid } from 'uuid'; import { within, userEvent } from '@storybook/testing-library'; +import { action } from '@storybook/addon-actions'; import type { PropsType } from './MyStories'; import enMessages from '../../_locales/en/messages.json'; import { MY_STORY_ID } from '../types/Stories'; @@ -24,50 +25,32 @@ export default { title: 'Components/MyStories', component: MyStories, argTypes: { - i18n: { - defaultValue: i18n, - }, - onBack: { - action: true, - }, - onDelete: { - action: true, - }, - onForward: { - action: true, - }, - onSave: { - action: true, - }, - ourConversationId: { - defaultValue: getDefaultConversation().id, - }, hasViewReceiptSetting: { control: 'boolean', - defaultValue: false, }, - queueStoryDownload: { - action: true, - }, - retryMessageSend: { - action: true, - }, - viewStory: { action: true }, }, -} as Meta; + args: { + i18n, + onBack: jest.fn(action('onBack')), + onDelete: action('onDelete'), + onForward: jest.fn(action('onForward')), + onSave: jest.fn(action('onSave')), + hasViewReceiptSetting: false, + queueStoryDownload: action('queueStoryDownload'), + retryMessageSend: action('retryMessageSend'), + viewStory: action('viewStory'), + }, +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; +const Template: StoryFn = args => ; export const NoStories = Template.bind({}); NoStories.args = { myStories: [], }; -NoStories.story = { - name: 'No Stories', -}; -const interactionTest: PlayFunction = async ({ +const interactionTest: PlayFunction = async ({ args, canvasElement, }) => { @@ -76,7 +59,7 @@ const interactionTest: PlayFunction = async ({ await userEvent.click(btnDownload); await expect(args.onSave).toHaveBeenCalled(); - const [btnBack] = canvas.getAllByLabelText('Back'); + const btnBack = canvas.getByText('Back'); await userEvent.click(btnBack); await expect(args.onBack).toHaveBeenCalled(); @@ -94,9 +77,6 @@ SingleListStories.args = { myStories: [getFakeMyStory(MY_STORY_ID)], }; SingleListStories.play = interactionTest; -SingleListStories.story = { - name: 'One distribution list', -}; export const MultiListStories = Template.bind({}); MultiListStories.args = { @@ -107,9 +87,6 @@ MultiListStories.args = { ], }; MultiListStories.play = interactionTest; -MultiListStories.story = { - name: 'Multiple distribution lists', -}; export const FailedSentStory = Template.bind({}); { diff --git a/ts/components/MyStoryButton.stories.tsx b/ts/components/MyStoryButton.stories.tsx index b49b3b0577..c4589f0e24 100644 --- a/ts/components/MyStoryButton.stories.tsx +++ b/ts/components/MyStoryButton.stories.tsx @@ -1,12 +1,13 @@ // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Meta, ReactFramework, Story } from '@storybook/react'; +import type { Meta, ReactRenderer, StoryFn } from '@storybook/react'; import type { PlayFunction } from '@storybook/csf'; import React from 'react'; -import { expect } from '@storybook/jest'; +import { expect, jest } from '@storybook/jest'; import { within, userEvent } from '@storybook/testing-library'; +import { action } from '@storybook/addon-actions'; import type { PropsType } from './MyStoryButton'; import enMessages from '../../_locales/en/messages.json'; import { MyStoryButton } from './MyStoryButton'; @@ -21,27 +22,21 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/MyStoriesButton', component: MyStoryButton, - argTypes: { - i18n: { - defaultValue: i18n, - }, - me: { - defaultValue: getDefaultConversation(), - }, - myStories: { - defaultValue: [getFakeMyStory()], - }, - onAddStory: { action: true }, - onClick: { action: true }, - queueStoryDownload: { action: true }, - showToast: { action: true }, + args: { + i18n, + me: getDefaultConversation(), + myStories: [getFakeMyStory()], + onAddStory: jest.fn(action('onAddStory')), + onClick: jest.fn(action('onClick')), + queueStoryDownload: action('queueStoryDownload'), + showToast: action('showToast'), }, -} as Meta; +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; +const Template: StoryFn = args => ; -const interactionTest: PlayFunction = async ({ +const interactionTest: PlayFunction = async ({ args, canvasElement, }) => { @@ -51,40 +46,34 @@ const interactionTest: PlayFunction = async ({ const textStory = canvas.getByText('Text story'); await userEvent.click(textStory); await expect(args.onAddStory).toHaveBeenCalled(); - const btnStory = canvas.getByText('My Stories'); - await userEvent.click(btnStory); - await expect(args.onClick).toHaveBeenCalled(); + if (args.myStories.length > 0) { + const btnStory = canvas.getByText('My Stories'); + await userEvent.click(btnStory); + await expect(args.onClick).toHaveBeenCalled(); + } }; export const NoStory = Template.bind({}); NoStory.args = { myStories: [], }; -NoStory.story = { - name: 'No Story', -}; + NoStory.play = interactionTest; export const OneStory = Template.bind({}); OneStory.args = {}; -OneStory.story = { - name: 'One Story', -}; + OneStory.play = interactionTest; export const ManyStories = Template.bind({}); ManyStories.args = { myStories: [getFakeMyStory(), getFakeMyStory()], }; -ManyStories.story = { - name: 'Many Stories', -}; + ManyStories.play = interactionTest; export const SendingStory = Template.bind({}); -SendingStory.story = { - name: 'Sending Story', -}; + { const myStory = getFakeMyStory(); SendingStory.args = { @@ -115,9 +104,7 @@ SendingStory.story = { SendingStory.play = interactionTest; export const FailedSendStory = Template.bind({}); -FailedSendStory.story = { - name: 'Failed Send Story', -}; + { const myStory = getFakeMyStory(); FailedSendStory.args = { diff --git a/ts/components/NewlyCreatedGroupInvitedContactsDialog.stories.tsx b/ts/components/NewlyCreatedGroupInvitedContactsDialog.stories.tsx index a503479024..c4c458e932 100644 --- a/ts/components/NewlyCreatedGroupInvitedContactsDialog.stories.tsx +++ b/ts/components/NewlyCreatedGroupInvitedContactsDialog.stories.tsx @@ -5,6 +5,8 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/react'; +import type { PropsType } from './NewlyCreatedGroupInvitedContactsDialog'; import { NewlyCreatedGroupInvitedContactsDialog } from './NewlyCreatedGroupInvitedContactsDialog'; import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -21,7 +23,7 @@ const conversations: Array = [ export default { title: 'Components/NewlyCreatedGroupInvitedContactsDialog', -}; +} satisfies Meta; export function OneContact(): JSX.Element { return ( @@ -35,10 +37,6 @@ export function OneContact(): JSX.Element { ); } -OneContact.story = { - name: 'One contact', -}; - export function TwoContacts(): JSX.Element { return ( ); } - -TwoContacts.story = { - name: 'Two contacts', -}; diff --git a/ts/components/NewlyCreatedGroupInvitedContactsDialog.tsx b/ts/components/NewlyCreatedGroupInvitedContactsDialog.tsx index e0c17032d7..51887d427a 100644 --- a/ts/components/NewlyCreatedGroupInvitedContactsDialog.tsx +++ b/ts/components/NewlyCreatedGroupInvitedContactsDialog.tsx @@ -12,7 +12,7 @@ import { ContactName } from './conversation/ContactName'; import { GroupDialog } from './GroupDialog'; import { openLinkInWebBrowser } from '../util/openLinkInWebBrowser'; -type PropsType = { +export type PropsType = { contacts: Array; getPreferredBadge: PreferredBadgeSelectorType; i18n: LocalizerType; diff --git a/ts/components/OutgoingGiftBadgeModal.stories.tsx b/ts/components/OutgoingGiftBadgeModal.stories.tsx index 8c9e22d44a..d5a85f5eab 100644 --- a/ts/components/OutgoingGiftBadgeModal.stories.tsx +++ b/ts/components/OutgoingGiftBadgeModal.stories.tsx @@ -2,12 +2,10 @@ // 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 './OutgoingGiftBadgeModal'; import { OutgoingGiftBadgeModal } from './OutgoingGiftBadgeModal'; - import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; import { BadgeCategory } from '../badges/BadgeCategory'; @@ -30,11 +28,8 @@ const getPreferredBadge = () => ({ }); const createProps = (overrideProps: Partial = {}): PropsType => ({ - recipientTitle: text( - 'recipientTitle', - overrideProps.recipientTitle || 'Default Name' - ), - badgeId: text('badgeId', overrideProps.badgeId || 'heart'), + recipientTitle: overrideProps.recipientTitle ?? 'Default Name', + badgeId: overrideProps.badgeId ?? 'heart', getPreferredBadge, hideOutgoingGiftBadgeModal: action('hideOutgoingGiftBadgeModal'), i18n, @@ -42,7 +37,9 @@ const createProps = (overrideProps: Partial = {}): PropsType => ({ export default { title: 'Components/OutgoingGiftBadgeModal', -}; + argTypes: {}, + args: {}, +} satisfies Meta; export function Normal(): JSX.Element { return ; @@ -56,7 +53,3 @@ export function MissingBadge(): JSX.Element { return ; } - -MissingBadge.story = { - name: 'Missing badge', -}; diff --git a/ts/components/PlaybackButton.stories.tsx b/ts/components/PlaybackButton.stories.tsx index 8080e83822..2ae8bffea4 100644 --- a/ts/components/PlaybackButton.stories.tsx +++ b/ts/components/PlaybackButton.stories.tsx @@ -4,12 +4,14 @@ import React from 'react'; import type { CSSProperties } from 'react'; import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/react'; +import type { ButtonProps } from './PlaybackButton'; import { PlaybackButton } from './PlaybackButton'; export default { title: 'components/PlaybackButton', component: PlaybackButton, -}; +} satisfies Meta; const rowStyles: CSSProperties = { display: 'flex', diff --git a/ts/components/PlaybackButton.tsx b/ts/components/PlaybackButton.tsx index 0f97944088..c66ac08aa8 100644 --- a/ts/components/PlaybackButton.tsx +++ b/ts/components/PlaybackButton.tsx @@ -12,7 +12,7 @@ const SPRING_CONFIG = { velocity: 0.01, }; -type ButtonProps = { +export type ButtonProps = { context?: 'incoming' | 'outgoing'; variant: 'message' | 'mini' | 'draft'; mod: 'play' | 'pause' | 'download' | 'pending'; diff --git a/ts/components/Preferences.stories.tsx b/ts/components/Preferences.stories.tsx index 88dcb62e5c..2d6f975d24 100644 --- a/ts/components/Preferences.stories.tsx +++ b/ts/components/Preferences.stories.tsx @@ -1,17 +1,17 @@ // Copyright 2021 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 React from 'react'; +import { action } from '@storybook/addon-actions'; import enMessages from '../../_locales/en/messages.json'; -import type { PropsDataType, PropsType } from './Preferences'; +import type { PropsType } from './Preferences'; import { Preferences } from './Preferences'; import { setupI18n } from '../util/setupI18n'; import { DEFAULT_CONVERSATION_COLOR } from '../types/Colors'; import { PhoneNumberSharingMode } from '../util/phoneNumberSharingMode'; import { PhoneNumberDiscoverability } from '../util/phoneNumberDiscoverability'; -import { objectMap } from '../util/objectMap'; import { DurationInSeconds } from '../util/durations'; const i18n = setupI18n('en', enMessages); @@ -44,145 +44,144 @@ const availableSpeakers = [ }, ]; -const getDefaultArgs = (): PropsDataType => ({ - availableCameras: [ - { - deviceId: - 'dfbe6effe70b0611ba0fdc2a9ea3f39f6cb110e6687948f7e5f016c111b7329c', - groupId: - '63ee218d2446869e40adfc958ff98263e51f74382b0143328ee4826f20a76f47', - kind: 'videoinput' as MediaDeviceKind, - label: 'FaceTime HD Camera (Built-in) (9fba:bced)', - }, - { - deviceId: - 'e2db196a31d50ff9b135299dc0beea67f65b1a25a06d8a4ce76976751bb7a08d', - groupId: - '218ba7f00d7b1239cca15b9116769e5e7d30cc01104ebf84d667643661e0ecf9', - kind: 'videoinput' as MediaDeviceKind, - label: 'Logitech Webcam (4e72:9058)', - }, - ], - availableMicrophones, - availableSpeakers, - blockedCount: 0, - customColors: {}, - defaultConversationColor: DEFAULT_CONVERSATION_COLOR, - deviceName: 'Work Windows ME', - hasAudioNotifications: true, - hasAutoDownloadUpdate: true, - hasAutoLaunch: true, - hasCallNotifications: true, - hasCallRingtoneNotification: false, - hasCountMutedConversations: false, - hasCustomTitleBar: true, - hasHideMenuBar: false, - hasIncomingCallNotifications: true, - hasLinkPreviews: true, - hasMediaCameraPermissions: true, - hasMediaPermissions: true, - hasMessageAudio: true, - hasMinimizeToAndStartInSystemTray: true, - hasMinimizeToSystemTray: true, - hasNotificationAttention: false, - hasNotifications: true, - hasReadReceipts: true, - hasRelayCalls: false, - hasSpellCheck: true, - hasStoriesDisabled: false, - hasTextFormatting: true, - hasTypingIndicators: true, - initialSpellCheckSetting: true, - isAutoDownloadUpdatesSupported: true, - isAutoLaunchSupported: true, - isFormattingFlagEnabled: true, - isHideMenuBarSupported: true, - isNotificationAttentionSupported: true, - isPhoneNumberSharingSupported: true, - isSyncSupported: true, - isSystemTraySupported: true, - isMinimizeToAndStartInSystemTraySupported: true, - lastSyncTime: Date.now(), - notificationContent: 'name', - selectedCamera: - 'dfbe6effe70b0611ba0fdc2a9ea3f39f6cb110e6687948f7e5f016c111b7329c', - selectedMicrophone: availableMicrophones[0], - selectedSpeaker: availableSpeakers[1], - shouldShowStoriesSettings: true, - sentMediaQualitySetting: 'standard', - themeSetting: 'system', - universalExpireTimer: DurationInSeconds.HOUR, - whoCanFindMe: PhoneNumberDiscoverability.Discoverable, - whoCanSeeMe: PhoneNumberSharingMode.Everybody, - zoomFactor: 1, -}); - -const defaultArgTypes: Record = {}; -objectMap(getDefaultArgs(), (key, defaultValue) => { - defaultArgTypes[key] = { defaultValue }; -}); - export default { title: 'Components/Preferences', component: Preferences, - argTypes: { - // ...defaultArgTypes, + args: { + i18n, - i18n: { - defaultValue: i18n, - }, + availableCameras: [ + { + deviceId: + 'dfbe6effe70b0611ba0fdc2a9ea3f39f6cb110e6687948f7e5f016c111b7329c', + groupId: + '63ee218d2446869e40adfc958ff98263e51f74382b0143328ee4826f20a76f47', + kind: 'videoinput' as MediaDeviceKind, + label: 'FaceTime HD Camera (Built-in) (9fba:bced)', + }, + { + deviceId: + 'e2db196a31d50ff9b135299dc0beea67f65b1a25a06d8a4ce76976751bb7a08d', + groupId: + '218ba7f00d7b1239cca15b9116769e5e7d30cc01104ebf84d667643661e0ecf9', + kind: 'videoinput' as MediaDeviceKind, + label: 'Logitech Webcam (4e72:9058)', + }, + ], + availableMicrophones, + availableSpeakers, + blockedCount: 0, + customColors: {}, + defaultConversationColor: DEFAULT_CONVERSATION_COLOR, + deviceName: 'Work Windows ME', + hasAudioNotifications: true, + hasAutoDownloadUpdate: true, + hasAutoLaunch: true, + hasCallNotifications: true, + hasCallRingtoneNotification: false, + hasCountMutedConversations: false, + hasCustomTitleBar: true, + hasHideMenuBar: false, + hasIncomingCallNotifications: true, + hasLinkPreviews: true, + hasMediaCameraPermissions: true, + hasMediaPermissions: true, + hasMessageAudio: true, + hasMinimizeToAndStartInSystemTray: true, + hasMinimizeToSystemTray: true, + hasNotificationAttention: false, + hasNotifications: true, + hasReadReceipts: true, + hasRelayCalls: false, + hasSpellCheck: true, + hasStoriesDisabled: false, + hasTextFormatting: true, + hasTypingIndicators: true, + initialSpellCheckSetting: true, + isAutoDownloadUpdatesSupported: true, + isAutoLaunchSupported: true, + isFormattingFlagEnabled: true, + isHideMenuBarSupported: true, + isNotificationAttentionSupported: true, + isPhoneNumberSharingSupported: true, + isSyncSupported: true, + isSystemTraySupported: true, + isMinimizeToAndStartInSystemTraySupported: true, + lastSyncTime: Date.now(), + notificationContent: 'name', + selectedCamera: + 'dfbe6effe70b0611ba0fdc2a9ea3f39f6cb110e6687948f7e5f016c111b7329c', + selectedMicrophone: availableMicrophones[0], + selectedSpeaker: availableSpeakers[1], + shouldShowStoriesSettings: true, + sentMediaQualitySetting: 'standard', + themeSetting: 'system', + universalExpireTimer: DurationInSeconds.HOUR, + whoCanFindMe: PhoneNumberDiscoverability.Discoverable, + whoCanSeeMe: PhoneNumberSharingMode.Everybody, + zoomFactor: 1, - addCustomColor: { action: true }, - closeSettings: { action: true }, - doDeleteAllData: { action: true }, - doneRendering: { action: true }, - editCustomColor: { action: true }, - executeMenuRole: { action: true }, - getConversationsWithCustomColor: { action: true }, - makeSyncRequest: { action: true }, - onAudioNotificationsChange: { action: true }, - onAutoDownloadUpdateChange: { action: true }, - onAutoLaunchChange: { action: true }, - onCallNotificationsChange: { action: true }, - onCallRingtoneNotificationChange: { action: true }, - onCountMutedConversationsChange: { action: true }, - onHasStoriesDisabledChanged: { action: true }, - onHideMenuBarChange: { action: true }, - onIncomingCallNotificationsChange: { action: true }, - onLastSyncTimeChange: { action: true }, - onMediaCameraPermissionsChange: { action: true }, - onMediaPermissionsChange: { action: true }, - onMessageAudioChange: { action: true }, - onMinimizeToAndStartInSystemTrayChange: { action: true }, - onMinimizeToSystemTrayChange: { action: true }, - onNotificationAttentionChange: { action: true }, - onNotificationContentChange: { action: true }, - onNotificationsChange: { action: true }, - onRelayCallsChange: { action: true }, - onSelectedCameraChange: { action: true }, - onSelectedMicrophoneChange: { action: true }, - onSelectedSpeakerChange: { action: true }, - onSentMediaQualityChange: { action: true }, - onSpellCheckChange: { action: true }, - onTextFormattingChange: { action: true }, - onThemeChange: { action: true }, - onUniversalExpireTimerChange: { action: true }, - onWhoCanSeeMeChange: { action: true }, - onWhoCanFindMeChange: { action: true }, - onZoomFactorChange: { action: true }, - removeCustomColor: { action: true }, - removeCustomColorOnConversations: { action: true }, - resetAllChatColors: { action: true }, - resetDefaultChatColor: { action: true }, - setGlobalDefaultConversationColor: { action: true }, - }, -} as Meta; + getConversationsWithCustomColor: () => Promise.resolve([]), + + addCustomColor: action('addCustomColor'), + closeSettings: action('closeSettings'), + doDeleteAllData: action('doDeleteAllData'), + doneRendering: action('doneRendering'), + editCustomColor: action('editCustomColor'), + executeMenuRole: action('executeMenuRole'), + makeSyncRequest: action('makeSyncRequest'), + onAudioNotificationsChange: action('onAudioNotificationsChange'), + onAutoDownloadUpdateChange: action('onAutoDownloadUpdateChange'), + onAutoLaunchChange: action('onAutoLaunchChange'), + onCallNotificationsChange: action('onCallNotificationsChange'), + onCallRingtoneNotificationChange: action( + 'onCallRingtoneNotificationChange' + ), + onCountMutedConversationsChange: action('onCountMutedConversationsChange'), + onHasStoriesDisabledChanged: action('onHasStoriesDisabledChanged'), + onHideMenuBarChange: action('onHideMenuBarChange'), + onIncomingCallNotificationsChange: action( + 'onIncomingCallNotificationsChange' + ), + onLastSyncTimeChange: action('onLastSyncTimeChange'), + onMediaCameraPermissionsChange: action('onMediaCameraPermissionsChange'), + onMediaPermissionsChange: action('onMediaPermissionsChange'), + onMessageAudioChange: action('onMessageAudioChange'), + onMinimizeToAndStartInSystemTrayChange: action( + 'onMinimizeToAndStartInSystemTrayChange' + ), + onMinimizeToSystemTrayChange: action('onMinimizeToSystemTrayChange'), + onNotificationAttentionChange: action('onNotificationAttentionChange'), + onNotificationContentChange: action('onNotificationContentChange'), + onNotificationsChange: action('onNotificationsChange'), + onRelayCallsChange: action('onRelayCallsChange'), + onSelectedCameraChange: action('onSelectedCameraChange'), + onSelectedMicrophoneChange: action('onSelectedMicrophoneChange'), + onSelectedSpeakerChange: action('onSelectedSpeakerChange'), + onSentMediaQualityChange: action('onSentMediaQualityChange'), + onSpellCheckChange: action('onSpellCheckChange'), + onTextFormattingChange: action('onTextFormattingChange'), + onThemeChange: action('onThemeChange'), + onUniversalExpireTimerChange: action('onUniversalExpireTimerChange'), + onWhoCanSeeMeChange: action('onWhoCanSeeMeChange'), + onWhoCanFindMeChange: action('onWhoCanFindMeChange'), + onZoomFactorChange: action('onZoomFactorChange'), + removeCustomColor: action('removeCustomColor'), + removeCustomColorOnConversations: action( + 'removeCustomColorOnConversations' + ), + resetAllChatColors: action('resetAllChatColors'), + resetDefaultChatColor: action('resetDefaultChatColor'), + setGlobalDefaultConversationColor: action( + 'setGlobalDefaultConversationColor' + ), + } satisfies PropsType, +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; +const Template: StoryFn = args => ; export const _Preferences = Template.bind({}); -_Preferences.args = getDefaultArgs(); export const Blocked1 = Template.bind({}); Blocked1.args = { @@ -198,9 +197,6 @@ export const CustomUniversalExpireTimer = Template.bind({}); CustomUniversalExpireTimer.args = { universalExpireTimer: DurationInSeconds.fromSeconds(9000), }; -CustomUniversalExpireTimer.story = { - name: 'Custom universalExpireTimer', -}; export const PNPSharingDisabled = Template.bind({}); PNPSharingDisabled.args = { @@ -208,9 +204,6 @@ PNPSharingDisabled.args = { whoCanFindMe: PhoneNumberDiscoverability.Discoverable, isPhoneNumberSharingSupported: true, }; -PNPSharingDisabled.story = { - name: 'PNP Sharing Disabled', -}; export const PNPDiscoverabilityDisabled = Template.bind({}); PNPDiscoverabilityDisabled.args = { @@ -218,9 +211,6 @@ PNPDiscoverabilityDisabled.args = { whoCanFindMe: PhoneNumberDiscoverability.NotDiscoverable, isPhoneNumberSharingSupported: true, }; -PNPDiscoverabilityDisabled.story = { - name: 'PNP Discoverability Disabled', -}; export const FormattingDisabled = Template.bind({}); FormattingDisabled.args = { diff --git a/ts/components/ProfileEditor.stories.tsx b/ts/components/ProfileEditor.stories.tsx index 32c388e2dc..9fe216309f 100644 --- a/ts/components/ProfileEditor.stories.tsx +++ b/ts/components/ProfileEditor.stories.tsx @@ -1,7 +1,7 @@ // Copyright 2021 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 { action } from '@storybook/addon-actions'; import React, { useState } from 'react'; import casual from 'casual'; @@ -18,6 +18,7 @@ import { } from '../state/ducks/usernameEnums'; import { getRandomColor } from '../test-both/helpers/getRandomColor'; import { setupI18n } from '../util/setupI18n'; +import { SignalService as Proto } from '../protobuf'; const i18n = setupI18n('en', enMessages); @@ -25,44 +26,11 @@ export default { component: ProfileEditor, title: 'Components/ProfileEditor', argTypes: { - aboutEmoji: { - defaultValue: '', - }, - aboutText: { - defaultValue: casual.sentence, - }, - profileAvatarPath: { - defaultValue: undefined, - }, - conversationId: { - defaultValue: generateUuid(), - }, - color: { - defaultValue: getRandomColor(), - }, - deleteAvatarFromDisk: { action: true }, - familyName: { - defaultValue: casual.last_name, - }, - firstName: { - defaultValue: casual.first_name, - }, - i18n: { - defaultValue: i18n, - }, - usernameLink: { - defaultValue: 'https://signal.me/#eu/testtest', - }, - usernameLinkFgColor: { - defaultValue: '', - }, isUsernameFlagEnabled: { control: { type: 'checkbox' }, - defaultValue: false, }, usernameEditState: { control: { type: 'radio' }, - defaultValue: UsernameEditState.Editing, options: { Editing: UsernameEditState.Editing, ConfirmingDelete: UsernameEditState.ConfirmingDelete, @@ -71,37 +39,49 @@ export default { }, usernameLinkState: { control: { type: 'select' }, - defaultValue: UsernameLinkState.Ready, options: [UsernameLinkState.Ready, UsernameLinkState.Updating], }, - onEditStateChanged: { action: true }, - onProfileChanged: { action: true }, - onSetSkinTone: { action: true }, - saveAttachment: { action: true }, - setUsernameLinkColor: { action: true }, - showToast: { action: true }, - recentEmojis: { - defaultValue: [], - }, - replaceAvatar: { action: true }, - resetUsernameLink: { action: true }, - saveAvatarToDisk: { action: true }, - markCompletedUsernameOnboarding: { action: true }, - markCompletedUsernameLinkOnboarding: { action: true }, - openUsernameReservationModal: { action: true }, - setUsernameEditState: { action: true }, - deleteUsername: { action: true }, - skinTone: { - defaultValue: 0, - }, - userAvatarData: { - defaultValue: [], - }, - username: { - defaultValue: undefined, - }, }, -} as Meta; + args: { + aboutEmoji: '', + aboutText: casual.sentence, + profileAvatarPath: undefined, + conversationId: generateUuid(), + color: getRandomColor(), + deleteAvatarFromDisk: action('deleteAvatarFromDisk'), + familyName: casual.last_name, + firstName: casual.first_name, + i18n, + + usernameLink: 'https://signal.me/#eu/testtest', + usernameLinkColor: Proto.AccountRecord.UsernameLink.Color.PURPLE, + isUsernameFlagEnabled: false, + usernameEditState: UsernameEditState.Editing, + usernameLinkState: UsernameLinkState.Ready, + + recentEmojis: [], + skinTone: 0, + userAvatarData: [], + username: undefined, + + onEditStateChanged: action('onEditStateChanged'), + onProfileChanged: action('onProfileChanged'), + onSetSkinTone: action('onSetSkinTone'), + saveAttachment: action('saveAttachment'), + setUsernameLinkColor: action('setUsernameLinkColor'), + showToast: action('showToast'), + replaceAvatar: action('replaceAvatar'), + resetUsernameLink: action('resetUsernameLink'), + saveAvatarToDisk: action('saveAvatarToDisk'), + markCompletedUsernameOnboarding: action('markCompletedUsernameOnboarding'), + markCompletedUsernameLinkOnboarding: action( + 'markCompletedUsernameLinkOnboarding' + ), + openUsernameReservationModal: action('openUsernameReservationModal'), + setUsernameEditState: action('setUsernameEditState'), + deleteUsername: action('deleteUsername'), + }, +} satisfies Meta; function renderEditUsernameModalBody(props: { onClose: () => void; @@ -122,7 +102,7 @@ function renderEditUsernameModalBody(props: { } // eslint-disable-next-line react/function-component-definition -const Template: Story = args => { +const Template: StoryFn = args => { const [skinTone, setSkinTone] = useState(0); return ( @@ -148,35 +128,22 @@ export const WithFullName = Template.bind({}); WithFullName.args = { familyName: casual.last_name, }; -WithFullName.story = { - name: 'with Full Name', -}; - export const WithCustomAbout = Template.bind({}); WithCustomAbout.args = { aboutEmoji: '🙏', aboutText: 'Live. Laugh. Love', }; -WithCustomAbout.story = { - name: 'with Custom About', -}; export const WithUsernameFlagEnabled = Template.bind({}); WithUsernameFlagEnabled.args = { isUsernameFlagEnabled: true, }; -WithUsernameFlagEnabled.story = { - name: 'with Username flag enabled', -}; export const WithUsernameFlagEnabledAndUsername = Template.bind({}); WithUsernameFlagEnabledAndUsername.args = { isUsernameFlagEnabled: true, username: 'signaluser.123', }; -WithUsernameFlagEnabledAndUsername.story = { - name: 'with Username flag enabled and username', -}; export const DeletingUsername = Template.bind({}); DeletingUsername.args = { diff --git a/ts/components/ProgressDialog.stories.tsx b/ts/components/ProgressDialog.stories.tsx index 88b5ced0a8..a4ebc51847 100644 --- a/ts/components/ProgressDialog.stories.tsx +++ b/ts/components/ProgressDialog.stories.tsx @@ -3,6 +3,7 @@ import * as React from 'react'; +import type { Meta } from '@storybook/react'; import type { PropsType } from './ProgressDialog'; import { ProgressDialog } from './ProgressDialog'; import { setupI18n } from '../util/setupI18n'; @@ -11,7 +12,7 @@ import enMessages from '../../_locales/en/messages.json'; export default { title: 'Components/ProgressDialog', -}; +} satisfies Meta; const i18n = setupI18n('en', enMessages); diff --git a/ts/components/ProgressModal.stories.tsx b/ts/components/ProgressModal.stories.tsx index ae82f83a00..3a770d717c 100644 --- a/ts/components/ProgressModal.stories.tsx +++ b/ts/components/ProgressModal.stories.tsx @@ -2,9 +2,9 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - +import type { Meta } from '@storybook/react'; +import type { PropsType } from './ProgressModal'; import { ProgressModal } from './ProgressModal'; - import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -12,7 +12,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/ProgressModal', -}; +} satisfies Meta; export function Normal(): JSX.Element { return ; diff --git a/ts/components/QrCode.stories.tsx b/ts/components/QrCode.stories.tsx index 380f7d09ab..6e91594591 100644 --- a/ts/components/QrCode.stories.tsx +++ b/ts/components/QrCode.stories.tsx @@ -2,12 +2,13 @@ // SPDX-License-Identifier: AGPL-3.0-only import React from 'react'; - +import type { Meta } from '@storybook/react'; +import type { PropsType } from './QrCode'; import { QrCode } from './QrCode'; export default { title: 'Components/QrCode', -}; +} satisfies Meta; export function Default(): JSX.Element { return ( diff --git a/ts/components/QrCode.tsx b/ts/components/QrCode.tsx index 416edb50e0..1d865ec99c 100644 --- a/ts/components/QrCode.tsx +++ b/ts/components/QrCode.tsx @@ -9,7 +9,7 @@ import { getEnvironment, Environment } from '../environment'; const AUTODETECT_TYPE_NUMBER = 0; const ERROR_CORRECTION_LEVEL = 'L'; -type PropsType = Readonly<{ +export type PropsType = Readonly<{ alt: string; className?: string; data: string | Uint8Array; diff --git a/ts/components/SafetyNumberChangeDialog.stories.tsx b/ts/components/SafetyNumberChangeDialog.stories.tsx index b9f5a3ce36..394c960ddb 100644 --- a/ts/components/SafetyNumberChangeDialog.stories.tsx +++ b/ts/components/SafetyNumberChangeDialog.stories.tsx @@ -3,7 +3,8 @@ import * as React from 'react'; import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; +import type { Props } from './SafetyNumberChangeDialog'; import { SafetyNumberChangeDialog } from './SafetyNumberChangeDialog'; import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation'; import { setupI18n } from '../util/setupI18n'; @@ -57,7 +58,7 @@ const useTheme = () => React.useContext(StorybookThemeContext); export default { title: 'Components/SafetyNumberChangeDialog', -}; +} satisfies Meta; export function SingleContactDialog(): JSX.Element { const theme = useTheme(); @@ -162,9 +163,6 @@ export function AllVerified(): JSX.Element { /> ); } -AllVerified.story = { - name: 'All verified; Send button instead', -}; export function MultipleContactsAllWithBadges(): JSX.Element { const theme = useTheme(); @@ -195,10 +193,6 @@ export function MultipleContactsAllWithBadges(): JSX.Element { ); } -MultipleContactsAllWithBadges.story = { - name: 'Multiple contacts, all with badges', -}; - export function TenContacts(): JSX.Element { const theme = useTheme(); return ( @@ -234,10 +228,6 @@ export function TenContacts(): JSX.Element { ); } -TenContacts.story = { - name: 'Ten contacts; first isReviewing = false, then scrolling dialog', -}; - export function NoContacts(): JSX.Element { const theme = useTheme(); return ( diff --git a/ts/components/SafetyNumberNotReady.stories.tsx b/ts/components/SafetyNumberNotReady.stories.tsx index 108d1c0b7d..dc3f3e51b5 100644 --- a/ts/components/SafetyNumberNotReady.stories.tsx +++ b/ts/components/SafetyNumberNotReady.stories.tsx @@ -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 './SafetyNumberNotReady'; import { SafetyNumberNotReady } from './SafetyNumberNotReady'; import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -12,12 +13,8 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/SafetyNumberNotReady', -}; +} satisfies Meta; export function Default(): JSX.Element { return ; } - -Default.story = { - name: 'Safety Number Not Ready', -}; diff --git a/ts/components/SafetyNumberOnboarding.stories.tsx b/ts/components/SafetyNumberOnboarding.stories.tsx index fd062bf0ab..0d81fb3535 100644 --- a/ts/components/SafetyNumberOnboarding.stories.tsx +++ b/ts/components/SafetyNumberOnboarding.stories.tsx @@ -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 './SafetyNumberOnboarding'; import { SafetyNumberOnboarding } from './SafetyNumberOnboarding'; import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -12,12 +13,8 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/SafetyNumberOnboarding', -}; +} satisfies Meta; export function Default(): JSX.Element { return ; } - -Default.story = { - name: 'Safety Number Onboarding', -}; diff --git a/ts/components/SafetyNumberViewer.stories.tsx b/ts/components/SafetyNumberViewer.stories.tsx index eb4766466e..e03c06722c 100644 --- a/ts/components/SafetyNumberViewer.stories.tsx +++ b/ts/components/SafetyNumberViewer.stories.tsx @@ -3,8 +3,7 @@ import * as React from 'react'; import { action } from '@storybook/addon-actions'; -import { boolean, text } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import type { PropsType } from './SafetyNumberViewer'; import { SafetyNumberViewer } from './SafetyNumberViewer'; import { setupI18n } from '../util/setupI18n'; @@ -78,26 +77,21 @@ const createProps = (overrideProps: Partial = {}): PropsType => ({ safetyNumbers: overrideProps.safetyNumbers ?? [ { identifierType: SafetyNumberIdentifierType.ACIIdentifier, - numberBlocks: text( - 'safetyNumber', - generateNumberBlocks().join(' ') - ).split(' '), + numberBlocks: generateNumberBlocks(), qrData: generateQRData(), }, ], toggleVerified: action('toggle-verified'), - verificationDisabled: boolean( - 'verificationDisabled', + verificationDisabled: overrideProps.verificationDisabled !== undefined ? overrideProps.verificationDisabled - : false - ), + : false, onClose: action('onClose'), }); export default { title: 'Components/SafetyNumberViewer', -}; +} satisfies Meta; export function SafetyNumber(): JSX.Element { return ; @@ -111,10 +105,7 @@ export function SafetyNumberBeforeE164Transition(): JSX.Element { safetyNumbers: [ { identifierType: SafetyNumberIdentifierType.E164Identifier, - numberBlocks: text( - 'safetyNumber', - generateNumberBlocks().join(' ') - ).split(' '), + numberBlocks: generateNumberBlocks(), qrData: generateQRData(), }, ], @@ -123,10 +114,6 @@ export function SafetyNumberBeforeE164Transition(): JSX.Element { ); } -SafetyNumberBeforeE164Transition.story = { - name: 'Safety Number (before e164 transition)', -}; - export function SafetyNumberE164Transition(): JSX.Element { return ( ); } - -NoPhoneNumberCannotVerify.story = { - name: 'No Phone Number (cannot verify)', -}; diff --git a/ts/components/Select.stories.tsx b/ts/components/Select.stories.tsx index 82b938b15b..d137fdd715 100644 --- a/ts/components/Select.stories.tsx +++ b/ts/components/Select.stories.tsx @@ -3,12 +3,13 @@ import React, { useState } from 'react'; import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; +import type { PropsType } from './Select'; import { Select } from './Select'; export default { title: 'Components/Select', -}; +} satisfies Meta; export function Normal(): JSX.Element { const [value, setValue] = useState(0); @@ -45,7 +46,3 @@ export function WithDisabledOptions(): JSX.Element { /> ); } - -WithDisabledOptions.story = { - name: 'With disabled options', -}; diff --git a/ts/components/SendStoryModal.stories.tsx b/ts/components/SendStoryModal.stories.tsx index dd87801a79..4d9a4f0dfc 100644 --- a/ts/components/SendStoryModal.stories.tsx +++ b/ts/components/SendStoryModal.stories.tsx @@ -1,9 +1,10 @@ // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Meta, Story } from '@storybook/react'; +import type { Meta } from '@storybook/react'; import React from 'react'; +import { action } from '@storybook/addon-actions'; import type { PropsType } from './SendStoryModal'; import enMessages from '../../_locales/en/messages.json'; import { SendStoryModal } from './SendStoryModal'; @@ -17,6 +18,7 @@ import { getFakeDistributionListsWithMembers, } from '../test-both/helpers/getFakeDistributionLists'; import { VIDEO_MP4 } from '../types/MIME'; +import type { StoryDistributionIdString } from '../types/StoryDistributionId'; const i18n = setupI18n('en', enMessages); @@ -28,99 +30,104 @@ const myStories = { export default { title: 'Components/SendStoryModal', component: SendStoryModal, - argTypes: { + args: { draftAttachment: { - defaultValue: { - contentType: VIDEO_MP4, - fileName: 'pixabay-Soap-Bubble-7141.mp4', - url: '/fixtures/pixabay-Soap-Bubble-7141.mp4', - }, + contentType: VIDEO_MP4, + fileName: 'pixabay-Soap-Bubble-7141.mp4', + url: '/fixtures/pixabay-Soap-Bubble-7141.mp4', + size: 1, }, - candidateConversations: { - defaultValue: Array.from(Array(100), () => getDefaultConversation()), - }, - distributionLists: { - defaultValue: [myStories], - }, - getPreferredBadge: { action: true }, - groupConversations: { - defaultValue: Array.from(Array(7), getDefaultGroup), - }, - groupStories: { - defaultValue: Array.from(Array(2), getDefaultGroup), - }, - hasFirstStoryPostExperience: { - defaultValue: false, - }, - i18n: { - defaultValue: i18n, - }, - me: { - defaultValue: getDefaultConversation(), - }, - onClose: { action: true }, - onDeleteList: { action: true }, - onDistributionListCreated: { action: true }, - onHideMyStoriesFrom: { action: true }, - onMediaPlaybackStart: { action: true }, - onSend: { action: true }, - onViewersUpdated: { action: true }, - setMyStoriesToAllSignalConnections: { action: true }, - mostRecentActiveStoryTimestampByGroupOrDistributionList: { - defaultValue: {}, - }, - signalConnections: { - defaultValue: Array.from(Array(42), getDefaultConversation), - }, - toggleGroupsForStorySend: { action: true }, - toggleSignalConnectionsModal: { action: true }, + candidateConversations: Array.from(Array(100), () => + getDefaultConversation() + ), + distributionLists: [myStories], + getPreferredBadge: () => undefined, + groupConversations: Array.from(Array(7), getDefaultGroup), + groupStories: Array.from(Array(2), getDefaultGroup), + hasFirstStoryPostExperience: false, + i18n, + me: getDefaultConversation(), + onClose: action('onClose'), + onDeleteList: action('onDeleteList'), + onDistributionListCreated: () => + Promise.resolve('' as StoryDistributionIdString), + onHideMyStoriesFrom: action('onHideMyStoriesFrom'), + onMediaPlaybackStart: action('onMediaPlaybackStart'), + onSend: action('onSend'), + onViewersUpdated: action('onViewersUpdated'), + setMyStoriesToAllSignalConnections: action( + 'setMyStoriesToAllSignalConnections' + ), + mostRecentActiveStoryTimestampByGroupOrDistributionList: {}, + signalConnections: Array.from(Array(42), getDefaultConversation), + toggleGroupsForStorySend: () => Promise.resolve(), + toggleSignalConnectionsModal: action('toggleSignalConnectionsModal'), }, -} as Meta; +} satisfies Meta; -// eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; +export function Modal(args: PropsType): JSX.Element { + return ( + + ); +} -export const Modal = Template.bind({}); -Modal.args = { - distributionLists: getFakeDistributionListsWithMembers(), -}; +export function BlockList(args: PropsType): JSX.Element { + return ( + + ); +} -export const BlockList = Template.bind({}); -BlockList.args = { - distributionLists: [ - { ...getMyStories(), members: [getDefaultConversation()] }, - ], - groupStories: [], -}; +export function AllowList(args: PropsType): JSX.Element { + return ( + + ); +} -export const AllowList = Template.bind({}); -AllowList.args = { - distributionLists: [ - { - ...getMyStories(), - isBlockList: false, - members: [getDefaultConversation()], - }, - ], - groupStories: [], -}; +export function FirstTime(args: PropsType): JSX.Element { + return ( + + ); +} -export const FirstTime = Template.bind({}); -FirstTime.args = { - distributionLists: [myStories], - groupStories: [], - hasFirstStoryPostExperience: true, -}; - -export const FirstTimeAlreadyConfiguredOnMobile = Template.bind({}); -FirstTime.args = { - distributionLists: [ - { - ...myStories, - isBlockList: false, - members: Array.from(Array(3), getDefaultConversation), - }, - ], - groupStories: [], - hasFirstStoryPostExperience: true, -}; +export function FirstTimeAlreadyConfiguredOnMobile( + args: PropsType +): JSX.Element { + return ( + + ); +} diff --git a/ts/components/ShortcutGuide.stories.tsx b/ts/components/ShortcutGuide.stories.tsx index 6766b930b1..7520b62d31 100644 --- a/ts/components/ShortcutGuide.stories.tsx +++ b/ts/components/ShortcutGuide.stories.tsx @@ -3,7 +3,7 @@ import * as 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 { Props } from './ShortcutGuide'; @@ -13,7 +13,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/ShortcutGuide', -}; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ i18n, diff --git a/ts/components/SignalConnectionsModal.stories.tsx b/ts/components/SignalConnectionsModal.stories.tsx index dec509bb5d..8060316221 100644 --- a/ts/components/SignalConnectionsModal.stories.tsx +++ b/ts/components/SignalConnectionsModal.stories.tsx @@ -1,9 +1,10 @@ // Copyright 2022 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 React from 'react'; +import { action } from '@storybook/addon-actions'; import type { PropsType } from './SignalConnectionsModal'; import enMessages from '../../_locales/en/messages.json'; import { SignalConnectionsModal } from './SignalConnectionsModal'; @@ -14,16 +15,16 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/SignalConnectionsModal', component: SignalConnectionsModal, - argTypes: { - i18n: { - defaultValue: i18n, - }, - onClose: { action: true }, + args: { + i18n, + onClose: action('onClose'), }, -} as Meta; +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; +const Template: StoryFn = args => ( + +); export const Modal = Template.bind({}); Modal.args = {}; diff --git a/ts/components/Slider.stories.tsx b/ts/components/Slider.stories.tsx index 897641df0b..c998aae130 100644 --- a/ts/components/Slider.stories.tsx +++ b/ts/components/Slider.stories.tsx @@ -5,12 +5,13 @@ import React, { useState } from 'react'; import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/react'; import type { PropsType } from './Slider'; import { Slider } from './Slider'; export default { title: 'Components/Slider', -}; +} satisfies Meta; const createProps = (): PropsType => ({ label: 'Slider Handle', diff --git a/ts/components/Spinner.stories.tsx b/ts/components/Spinner.stories.tsx index bd73aa4e75..eeda198765 100644 --- a/ts/components/Spinner.stories.tsx +++ b/ts/components/Spinner.stories.tsx @@ -2,58 +2,43 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - -import { select, text } from '@storybook/addon-knobs'; +import type { Meta } from '@storybook/react'; import type { Props } from './Spinner'; import { Spinner, SpinnerDirections, SpinnerSvgSizes } from './Spinner'; export default { title: 'Components/Spinner', -}; + argTypes: { + size: { control: { type: 'text' } }, + svgSize: { control: { type: 'select' }, options: SpinnerSvgSizes }, + direction: { control: { type: 'select' }, options: SpinnerDirections }, + }, + args: { + size: '20px', + svgSize: 'normal', + direction: undefined, + }, +} satisfies Meta; -const createProps = (overrideProps: Partial = {}): Props => ({ - size: text('size', overrideProps.size || ''), - svgSize: select( - 'svgSize', - SpinnerSvgSizes.reduce((m, s) => ({ ...m, [s]: s }), {}), - overrideProps.svgSize || 'normal' - ), - direction: select( - 'direction', - SpinnerDirections.reduce((d, s) => ({ ...d, [s]: s }), {}), - overrideProps.direction - ), -}); - -export function Normal(): JSX.Element { - const props = createProps(); - - return ; +export function Normal(args: Props): JSX.Element { + return ; } -export function SvgSizes(): JSX.Element { - const props = createProps(); - +export function SvgSizes(args: Props): JSX.Element { return ( <> {SpinnerSvgSizes.map(svgSize => ( - + ))} ); } -SvgSizes.story = { - name: 'SVG Sizes', -}; - -export function Directions(): JSX.Element { - const props = createProps(); - +export function Directions(args: Props): JSX.Element { return ( <> {SpinnerDirections.map(direction => ( - + ))} ); diff --git a/ts/components/StoriesSettingsModal.stories.tsx b/ts/components/StoriesSettingsModal.stories.tsx index 7343197e1d..de1848c85e 100644 --- a/ts/components/StoriesSettingsModal.stories.tsx +++ b/ts/components/StoriesSettingsModal.stories.tsx @@ -1,9 +1,10 @@ // Copyright 2022 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 React from 'react'; +import { action } from '@storybook/addon-actions'; import type { PropsType } from './StoriesSettingsModal'; import enMessages from '../../_locales/en/messages.json'; import { StoriesSettingsModal } from './StoriesSettingsModal'; @@ -23,45 +24,37 @@ export default { title: 'Components/StoriesSettingsModal', component: StoriesSettingsModal, argTypes: { - candidateConversations: { - defaultValue: Array.from(Array(100), () => getDefaultConversation()), - }, - signalConnections: { - defaultValue: Array.from(Array(42), getDefaultConversation), - }, - distributionLists: { - defaultValue: [], - }, - groupStories: { - defaultValue: Array.from(Array(2), getDefaultGroup), - }, - getPreferredBadge: { action: true }, - hideStoriesSettings: { action: true }, - i18n: { - defaultValue: i18n, - }, - me: { - defaultValue: getDefaultConversation(), - }, storyViewReceiptsEnabled: { control: 'boolean' }, - onDeleteList: { action: true }, - toggleGroupsForStorySend: { action: true }, - onDistributionListCreated: { action: true }, - onHideMyStoriesFrom: { action: true }, - onRemoveMembers: { action: true }, - onRepliesNReactionsChanged: { action: true }, - onViewersUpdated: { action: true }, - setMyStoriesToAllSignalConnections: { action: true }, - toggleSignalConnectionsModal: { action: true }, - setStoriesDisabled: { action: true }, - getConversationByServiceId: { - defaultValue: () => getDefaultGroup(), - }, }, -} as Meta; + args: { + candidateConversations: Array.from(Array(100), () => + getDefaultConversation() + ), + signalConnections: Array.from(Array(42), getDefaultConversation), + distributionLists: [], + groupStories: Array.from(Array(2), getDefaultGroup), + getPreferredBadge: () => undefined, + hideStoriesSettings: action('hideStoriesSettings'), + i18n, + me: getDefaultConversation(), + onDeleteList: action('onDeleteList'), + toggleGroupsForStorySend: action('toggleGroupsForStorySend'), + onDistributionListCreated: () => Promise.resolve(''), + onHideMyStoriesFrom: action('onHideMyStoriesFrom'), + onRemoveMembers: action('onRemoveMembers'), + onRepliesNReactionsChanged: action('onRepliesNReactionsChanged'), + onViewersUpdated: action('onViewersUpdated'), + setMyStoriesToAllSignalConnections: action( + 'setMyStoriesToAllSignalConnections' + ), + toggleSignalConnectionsModal: action('toggleSignalConnectionsModal'), + setStoriesDisabled: action('setStoriesDisabled'), + getConversationByServiceId: () => getDefaultGroup(), + }, +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; +const Template: StoryFn = args => ; export const MyStories = Template.bind({}); { diff --git a/ts/components/StoriesTab.stories.tsx b/ts/components/StoriesTab.stories.tsx index acb2beffec..9a3f37a155 100644 --- a/ts/components/StoriesTab.stories.tsx +++ b/ts/components/StoriesTab.stories.tsx @@ -1,9 +1,10 @@ // Copyright 2022 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 React from 'react'; +import { action } from '@storybook/addon-actions'; import type { PropsType } from './StoriesTab'; import { StoriesTab } from './StoriesTab'; import enMessages from '../../_locales/en/messages.json'; @@ -20,50 +21,32 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/StoriesTab', component: StoriesTab, - argTypes: { - deleteStoryForEveryone: { action: true }, - getPreferredBadge: { action: true }, - hiddenStories: { - defaultValue: [], - }, - i18n: { - defaultValue: i18n, - }, - maxAttachmentSizeInKb: { - defaultValue: 100 * 1024, - }, - me: { - defaultValue: getDefaultConversation(), - }, - myStories: { - defaultValue: [], - }, - onForwardStory: { action: true }, - onSaveStory: { action: true }, - ourConversationId: { - defaultValue: getDefaultConversation().id, - }, - preferredWidthFromStorage: { - defaultValue: 380, - }, - queueStoryDownload: { action: true }, - renderStoryCreator: { action: true }, - retryMessageSend: { action: true }, - showConversation: { action: true }, - showStoriesSettings: { action: true }, - showToast: { action: true }, - stories: { - defaultValue: [], - }, - toggleHideStories: { action: true }, - toggleStoriesView: { action: true }, - viewUserStories: { action: true }, - viewStory: { action: true }, + args: { + deleteStoryForEveryone: action('deleteStoryForEveryone'), + getPreferredBadge: () => undefined, + hiddenStories: [], + i18n, + maxAttachmentSizeInKb: 100 * 1024, + me: getDefaultConversation(), + myStories: [], + onForwardStory: action('onForwardStory'), + onSaveStory: action('onSaveStory'), + preferredWidthFromStorage: 380, + queueStoryDownload: action('queueStoryDownload'), + renderStoryCreator: () => <>StoryCreator, + retryMessageSend: action('retryMessageSend'), + showConversation: action('showConversation'), + showStoriesSettings: action('showStoriesSettings'), + showToast: action('showToast'), + stories: [], + toggleHideStories: action('toggleHideStories'), + viewUserStories: action('viewUserStories'), + viewStory: action('viewStory'), }, -} as Meta; +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; +const Template: StoryFn = args => ; export const Blank = Template.bind({}); Blank.args = {}; diff --git a/ts/components/StoryCreator.stories.tsx b/ts/components/StoryCreator.stories.tsx index 35f35aaa2b..ff1f7720f3 100644 --- a/ts/components/StoryCreator.stories.tsx +++ b/ts/components/StoryCreator.stories.tsx @@ -1,9 +1,10 @@ // Copyright 2022 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 React from 'react'; +import { action } from '@storybook/addon-actions'; import type { PropsType } from './StoryCreator'; import enMessages from '../../_locales/en/messages.json'; import { StoryCreator } from './StoryCreator'; @@ -20,71 +21,46 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/StoryCreator', component: StoryCreator, - argTypes: { - candidateConversations: { - defaultValue: Array.from(Array(100), getDefaultConversation), - }, - debouncedMaybeGrabLinkPreview: { action: true }, - distributionLists: { defaultValue: getFakeDistributionListsWithMembers() }, - getPreferredBadge: { action: true }, - groupConversations: { - defaultValue: Array.from(Array(7), getDefaultGroup), - }, - groupStories: { - defaultValue: Array.from(Array(4), getDefaultGroup), - }, - hasFirstStoryPostExperience: { - defaultValue: false, - }, - i18n: { defaultValue: i18n }, + args: { + candidateConversations: Array.from(Array(100), getDefaultConversation), + debouncedMaybeGrabLinkPreview: action('debouncedMaybeGrabLinkPreview'), + distributionLists: getFakeDistributionListsWithMembers(), + getPreferredBadge: () => undefined, + groupConversations: Array.from(Array(7), getDefaultGroup), + groupStories: Array.from(Array(4), getDefaultGroup), + hasFirstStoryPostExperience: false, + i18n, imageToBlurHash: async () => 'LDA,FDBnm+I=p{tkIUI;~UkpELV]', - installedPacks: { - defaultValue: [], - }, - isSending: { - defaultValue: false, - }, - linkPreview: { - defaultValue: undefined, - }, - me: { - defaultValue: getDefaultConversation(), - }, - onClose: { action: true }, - onDeleteList: { action: true }, - onDistributionListCreated: { action: true }, - onHideMyStoriesFrom: { action: true }, - onSend: { action: true }, - onSetSkinTone: { action: true }, - onUseEmoji: { action: true }, - onViewersUpdated: { action: true }, - processAttachment: { action: true }, - recentEmojis: { - defaultValue: [], - }, - recentStickers: { - defaultValue: [], - }, - sendStoryModalOpenStateChanged: { action: true }, - setMyStoriesToAllSignalConnections: { action: true }, - signalConnections: { - defaultValue: Array.from(Array(42), getDefaultConversation), - }, - skinTone: { - defaultValue: 0, - }, - toggleSignalConnectionsModal: { action: true }, + installedPacks: [], + isSending: false, + linkPreview: undefined, + me: getDefaultConversation(), + onClose: action('onClose'), + onDeleteList: action('onDeleteList'), + onDistributionListCreated: undefined, + onHideMyStoriesFrom: action('onHideMyStoriesFrom'), + onSend: action('onSend'), + onSetSkinTone: action('onSetSkinTone'), + onUseEmoji: action('onUseEmoji'), + onViewersUpdated: action('onViewersUpdated'), + processAttachment: undefined, + recentEmojis: [], + recentStickers: [], + sendStoryModalOpenStateChanged: action('sendStoryModalOpenStateChanged'), + setMyStoriesToAllSignalConnections: action( + 'setMyStoriesToAllSignalConnections' + ), + signalConnections: Array.from(Array(42), getDefaultConversation), + skinTone: 0, + toggleSignalConnectionsModal: action('toggleSignalConnectionsModal'), }, -} as Meta; +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; +const Template: StoryFn = args => ; export const Default = Template.bind({}); Default.args = {}; -Default.story = { - name: 'w/o Link Preview available', -}; export const LinkPreview = Template.bind({}); LinkPreview.args = { @@ -97,17 +73,11 @@ LinkPreview.args = { url: 'https://www.catsandkittens.lolcats/kittens/page/1', }, }; -LinkPreview.story = { - name: 'with Link Preview ready to be applied', -}; export const FirstTime = Template.bind({}); FirstTime.args = { hasFirstStoryPostExperience: true, }; -FirstTime.story = { - name: 'First time posting a story', -}; export const Sending = Template.bind({}); Sending.args = { diff --git a/ts/components/StoryDetailsModal.stories.tsx b/ts/components/StoryDetailsModal.stories.tsx index 50b42db17f..63b9e7e6ca 100644 --- a/ts/components/StoryDetailsModal.stories.tsx +++ b/ts/components/StoryDetailsModal.stories.tsx @@ -1,15 +1,15 @@ // Copyright 2022 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 React from 'react'; import casual from 'casual'; +import { action } from '@storybook/addon-actions'; import type { PropsType } from './StoryDetailsModal'; import enMessages from '../../_locales/en/messages.json'; import { SendStatus } from '../messages/MessageSendState'; import { StoryDetailsModal } from './StoryDetailsModal'; -import { fakeAttachment } from '../test-both/helpers/fakeAttachment'; import { getDefaultConversation } from '../test-both/helpers/getDefaultConversation'; import { setupI18n } from '../util/setupI18n'; @@ -18,29 +18,18 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/StoryDetailsModal', component: StoryDetailsModal, - argTypes: { - getPreferredBadge: { action: true }, - i18n: { - defaultValue: i18n, - }, - onClose: { action: true }, - sender: { - defaultValue: getDefaultConversation(), - }, - sendState: { - defaultValue: undefined, - }, - size: { - defaultValue: fakeAttachment().size, - }, - timestamp: { - defaultValue: Date.now(), - }, + args: { + getPreferredBadge: () => undefined, + i18n, + onClose: action('onClose'), + sender: getDefaultConversation(), + sendState: undefined, + timestamp: Date.now(), }, -} as Meta; +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; +const Template: StoryFn = args => ; export const MyStory = Template.bind({}); MyStory.args = { diff --git a/ts/components/StoryImage.stories.tsx b/ts/components/StoryImage.stories.tsx index e50c6b47cd..909a4fe536 100644 --- a/ts/components/StoryImage.stories.tsx +++ b/ts/components/StoryImage.stories.tsx @@ -6,6 +6,7 @@ import { v4 as uuid } from 'uuid'; import { action } from '@storybook/addon-actions'; import { noop } from 'lodash'; +import type { Meta } from '@storybook/react'; import type { PropsType } from './StoryImage'; import { StoryImage } from './StoryImage'; import enMessages from '../../_locales/en/messages.json'; @@ -20,7 +21,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/StoryImage', -}; +} satisfies Meta; function getDefaultProps(): PropsType { return { @@ -41,26 +42,14 @@ export function GoodStory(): JSX.Element { return ; } -GoodStory.story = { - name: 'Good story', -}; - export function GoodStoryThumbnail(): JSX.Element { return ; } -GoodStoryThumbnail.story = { - name: 'Good story (thumbnail)', -}; - export function NotDownloaded(): JSX.Element { return ; } -NotDownloaded.story = { - name: 'Not downloaded', -}; - export function NotDownloadedThumbnail(): JSX.Element { return ( { + log.error( + 'StoryImage: Failed to play video', + Errors.toLogFormat(error) + ); + }); } }, [isPaused, onMediaPlaybackStart]); diff --git a/ts/components/StoryLinkPreview.stories.tsx b/ts/components/StoryLinkPreview.stories.tsx index 8bffcea296..b46ba21e41 100644 --- a/ts/components/StoryLinkPreview.stories.tsx +++ b/ts/components/StoryLinkPreview.stories.tsx @@ -1,7 +1,7 @@ // 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 React from 'react'; import type { Props } from './StoryLinkPreview'; @@ -21,35 +21,23 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/StoryLinkPreview', component: StoryLinkPreview, - argTypes: { - description: { - defaultValue: - 'Introducing Mac Studio. Stunningly compact. Endless connectivity. And astonishing performance with M1 Max or the new M1 Ultra chip.', - }, - forceCompactMode: { - defaultValue: false, - }, - i18n: { - defaultValue: i18n, - }, - image: { - defaultValue: fakeAttachment({ - // url: 'https://www.apple.com/v/mac-studio/c/images/meta/mac-studio_overview__eedzbosm1t26_og.png', - url: '/fixtures/kitten-4-112-112.jpg', - contentType: IMAGE_JPEG, - }), - }, - title: { - defaultValue: 'Mac Studio', - }, - url: { - defaultValue: 'https://www.apple.com/mac-studio/', - }, + args: { + description: + 'Introducing Mac Studio. Stunningly compact. Endless connectivity. And astonishing performance with M1 Max or the new M1 Ultra chip.', + forceCompactMode: false, + i18n, + image: fakeAttachment({ + // url: 'https://www.apple.com/v/mac-studio/c/images/meta/mac-studio_overview__eedzbosm1t26_og.png', + url: '/fixtures/kitten-4-112-112.jpg', + contentType: IMAGE_JPEG, + }), + title: 'Mac Studio', + url: 'https://www.apple.com/mac-studio/', }, -} as Meta; +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; +const Template: StoryFn = args => ; export const Default = Template.bind({}); diff --git a/ts/components/StoryListItem.stories.tsx b/ts/components/StoryListItem.stories.tsx index 14c85c49cb..4a2f0d97b4 100644 --- a/ts/components/StoryListItem.stories.tsx +++ b/ts/components/StoryListItem.stories.tsx @@ -1,9 +1,10 @@ // Copyright 2022 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 React from 'react'; +import { action } from '@storybook/addon-actions'; import type { PropsType } from './StoryListItem'; import { StoryListItem } from './StoryListItem'; import enMessages from '../../_locales/en/messages.json'; @@ -19,30 +20,26 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/StoryListItem', component: StoryListItem, - argTypes: { - conversationId: { - defaultValue: getDefaultConversation().id, - }, - getPreferredBadge: { action: true }, - i18n: { - defaultValue: i18n, - }, - onGoToConversation: { action: true }, - onHideStory: { action: true }, - queueStoryDownload: { action: true }, + args: { + conversationId: getDefaultConversation().id, + getPreferredBadge: () => undefined, + i18n, + onGoToConversation: action('onGoToConversation'), + onHideStory: action('onHideStory'), + queueStoryDownload: action('queueStoryDownload'), story: { - defaultValue: { - messageId: '123', - sender: getDefaultConversation(), - timestamp: Date.now(), - }, + messageId: '123', + sender: getDefaultConversation(), + timestamp: Date.now(), + messageIdForLogging: '123', + expirationTimestamp: undefined, }, - viewUserStories: { action: true }, + viewUserStories: action('viewUserStories'), }, -} as Meta; +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; +const Template: StoryFn = args => ; export const SomeonesStory = Template.bind({}); SomeonesStory.args = { @@ -60,6 +57,3 @@ SomeonesStory.args = { expirationTimestamp: undefined, }, }; -SomeonesStory.story = { - name: "Someone's story", -}; diff --git a/ts/components/StoryViewer.stories.tsx b/ts/components/StoryViewer.stories.tsx index 0c21442de8..1605b7e96a 100644 --- a/ts/components/StoryViewer.stories.tsx +++ b/ts/components/StoryViewer.stories.tsx @@ -1,9 +1,9 @@ // Copyright 2022 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only -import type { Meta, Story } from '@storybook/react'; +import type { Meta } from '@storybook/react'; import React from 'react'; - +import { action } from '@storybook/addon-actions'; import type { PropsType } from './StoryViewer'; import enMessages from '../../_locales/en/messages.json'; import { SendStatus } from '../messages/MessageSendState'; @@ -22,244 +22,237 @@ export default { title: 'Components/StoryViewer', component: StoryViewer, argTypes: { - currentIndex: { - defaultvalue: 0, - }, - getPreferredBadge: { action: true }, - group: { - defaultValue: undefined, - }, - hasAllStoriesMuted: { + hasAllStoriesUnmuted: { control: 'boolean', - defaultValue: false, }, hasViewReceiptSetting: { control: 'boolean', - defaultValue: true, }, - i18n: { - defaultValue: i18n, - }, - platform: { - defaultValue: 'darwin', - }, - loadStoryReplies: { action: true }, - markStoryRead: { action: true }, - numStories: { - defaultValue: 1, - }, - onGoToConversation: { action: true }, - onHideStory: { action: true }, - onReactToStory: { action: true }, - onReplyToStory: { action: true }, - onSetSkinTone: { action: true }, - onTextTooLong: { action: true }, - onUseEmoji: { action: true }, - preferredReactionEmoji: { - defaultValue: ['❤️', '👍', '👎', '😂', '😮', '😢'], - }, - queueStoryDownload: { action: true }, - renderEmojiPicker: { action: true }, - retryMessageSend: { action: true }, - showToast: { action: true }, - skinTone: { - defaultValue: 0, - }, - story: { - defaultValue: getFakeStoryView(), - }, - storyViewMode: { - defaultValue: StoryViewModeType.All, - }, - toggleHasAllStoriesMuted: { action: true }, - viewStory: { action: true }, - isWindowActive: { defaultValue: true }, }, args: { currentIndex: 0, + getPreferredBadge: () => undefined, + group: undefined, + hasAllStoriesUnmuted: true, + hasViewReceiptSetting: true, + i18n, + platform: 'darwin', + loadStoryReplies: action('loadStoryReplies'), + markStoryRead: action('markStoryRead'), + numStories: 1, + onGoToConversation: action('onGoToConversation'), + onHideStory: action('onHideStory'), + onReactToStory: action('onReactToStory'), + onReplyToStory: action('onReplyToStory'), + onSetSkinTone: action('onSetSkinTone'), + onTextTooLong: action('onTextTooLong'), + onUseEmoji: action('onUseEmoji'), + onMediaPlaybackStart: action('onMediaPlaybackStart'), + preferredReactionEmoji: ['❤️', '👍', '👎', '😂', '😮', '😢'], + queueStoryDownload: action('queueStoryDownload'), + renderEmojiPicker: () => <>EmojiPicker, + retryMessageSend: action('retryMessageSend'), + showToast: action('showToast'), + skinTone: 0, + story: getFakeStoryView(), + storyViewMode: StoryViewModeType.All, + viewStory: action('viewStory'), + isWindowActive: true, }, -} as Meta; +} satisfies Meta; -// eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; +export function SomeonesStory(args: PropsType): JSX.Element { + return ; +} -export const SomeonesStory = Template.bind({}); -SomeonesStory.args = {}; -SomeonesStory.story = { - name: "Someone's story", -}; +export function WideStory(args: PropsType): JSX.Element { + return ( + + ); +} -export const WideStory = Template.bind({}); -WideStory.args = { - story: getFakeStoryView('/fixtures/nathan-anderson-316188-unsplash.jpg'), -}; -WideStory.story = { - name: 'Wide story', -}; +export function InAGroup(args: PropsType): JSX.Element { + return ( + + ); +} -export const InAGroup = Template.bind({}); -InAGroup.args = { - group: getDefaultConversation({ - avatarPath: '/fixtures/kitten-4-112-112.jpg', - title: 'Family Group', - type: 'group', - }), -}; -InAGroup.story = { - name: 'In a group', -}; +export function MultiStory(args: PropsType): JSX.Element { + return ( + + ); +} -export const MultiStory = Template.bind({}); -MultiStory.args = { - currentIndex: 2, - numStories: 7, - story: { - ...getFakeStoryView(), - attachment: fakeAttachment({ - contentType: VIDEO_MP4, - fileName: 'pixabay-Soap-Bubble-7141.mp4', - url: '/fixtures/kitten-4-112-112.jpg', - screenshotPath: '/fixtures/kitten-4-112-112.jpg', - }), - }, -}; -MultiStory.story = { - name: 'Multi story', -}; +export function Caption(args: PropsType): JSX.Element { + return ( + + ); +} -export const Caption = Template.bind({}); -Caption.args = { - story: { - ...getFakeStoryView(), - attachment: fakeAttachment({ - caption: 'This place looks lovely', - path: 'file.jpg', - url: '/fixtures/nathan-anderson-316188-unsplash.jpg', - }), - }, -}; +export function EmojiCaption(args: PropsType): JSX.Element { + return ( + + ); +} -export const EmojiCaption = Template.bind({}); -EmojiCaption.args = { - story: { - ...getFakeStoryView(), - attachment: fakeAttachment({ - caption: 'WOOOOOOOOW 🥰', - path: 'file.jpg', - url: '/fixtures/nathan-anderson-316188-unsplash.jpg', - }), - }, -}; +export function LongCaption(args: PropsType): JSX.Element { + return ( + + ); +} -export const LongCaption = Template.bind({}); -LongCaption.args = { - story: { - ...getFakeStoryView(), - attachment: fakeAttachment({ - caption: - 'Snowycle, snowycle, snowycle\nI want to ride my snowycle, snowycle, snowycle\nI want to ride my snowycle\nI want to ride my snow\nI want to ride my snowycle\nI want to ride it where I like\nSnowycle, snowycle, snowycle\nI want to ride my snowycle, snowycle, snowycle\nI want to ride my snowycle\nI want to ride my snow\nI want to ride my snowycle\nI want to ride it where I like\nSnowycle, snowycle, snowycle\nI want to ride my snowycle, snowycle, snowycle\nI want to ride my snowycle\nI want to ride my snow\nI want to ride my snowycle\nI want to ride it where I like', - path: 'file.jpg', - url: '/fixtures/snow.jpg', - }), - }, -}; +export function YourStory(args: PropsType): JSX.Element { + const storyView = getFakeStoryView( + '/fixtures/nathan-anderson-316188-unsplash.jpg' + ); + return ( + + ); +} -export const YourStory = Template.bind({}); -{ +export function YourStoryFailed(args: PropsType): JSX.Element { const storyView = getFakeStoryView( '/fixtures/nathan-anderson-316188-unsplash.jpg' ); - YourStory.args = { - distributionList: { - id: generateStoryDistributionId(), - name: 'Close Friends', - }, - story: { - ...storyView, - sender: { - ...storyView.sender, - isMe: true, - }, - sendState: [ - { - recipient: getDefaultConversation(), - status: SendStatus.Viewed, + return ( + + ); } -export const YourStoryFailed = Template.bind({}); -{ +export function ReadReceiptsOff(args: PropsType): JSX.Element { const storyView = getFakeStoryView( '/fixtures/nathan-anderson-316188-unsplash.jpg' ); - - YourStoryFailed.args = { - distributionList: { - id: generateStoryDistributionId(), - name: 'Close Friends', - }, - story: { - ...storyView, - sender: { - ...storyView.sender, - isMe: true, - }, - sendState: [ - { - recipient: getDefaultConversation(), - status: SendStatus.Viewed, + return ( + ); - ReadReceiptsOff.args = { - hasViewReceiptSetting: false, - story: { - ...storyView, - sender: { - ...storyView.sender, - isMe: true, - }, - sendState: [ - { - recipient: getDefaultConversation(), - status: SendStatus.Viewed, - }, - { - recipient: getDefaultConversation(), - status: SendStatus.Delivered, - }, - { - recipient: getDefaultConversation(), - status: SendStatus.Pending, - }, - ], - }, - }; } -ReadReceiptsOff.storyName = 'Read receipts turned off'; diff --git a/ts/components/StoryViewsNRepliesModal.stories.tsx b/ts/components/StoryViewsNRepliesModal.stories.tsx index 48e77d77a1..c86261be69 100644 --- a/ts/components/StoryViewsNRepliesModal.stories.tsx +++ b/ts/components/StoryViewsNRepliesModal.stories.tsx @@ -1,11 +1,12 @@ // Copyright 2022 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 React from 'react'; import { v4 as generateUuid } from 'uuid'; import { useArgs } from '@storybook/addons'; +import { action } from '@storybook/addon-actions'; import type { PropsType } from './StoryViewsNRepliesModal'; import * as durations from '../util/durations'; import enMessages from '../../_locales/en/messages.json'; @@ -21,51 +22,35 @@ export default { title: 'Components/StoryViewsNRepliesModal', component: StoryViewsNRepliesModal, argTypes: { - authorTitle: { - defaultValue: getDefaultConversation().title, - }, - canReply: { - defaultValue: true, - }, - getPreferredBadge: { action: true }, hasViewReceiptSetting: { control: 'boolean', - defaultValue: true, }, hasViewsCapability: { control: 'boolean', - defaultValue: false, - }, - i18n: { - defaultValue: i18n, - }, - platform: { - defaultValue: 'darwin', - }, - onClose: { action: true }, - onSetSkinTone: { action: true }, - onReact: { action: true }, - onReply: { action: true }, - onTextTooLong: { action: true }, - onUseEmoji: { action: true }, - preferredReactionEmoji: { - defaultValue: ['❤️', '👍', '👎', '😂', '😮', '😢'], - }, - renderEmojiPicker: { action: true }, - replies: { - defaultValue: [], - }, - views: { - defaultValue: [], - }, - viewTarget: { - defaultValue: StoryViewTargetType.Views, - }, - onChangeViewTarget: { - action: true, }, }, -} as Meta; + args: { + authorTitle: getDefaultConversation().title, + canReply: true, + getPreferredBadge: () => undefined, + hasViewReceiptSetting: true, + hasViewsCapability: false, + i18n, + platform: 'darwin', + onClose: action('onClose'), + onSetSkinTone: action('onSetSkinTone'), + onReact: action('onReact'), + onReply: action('onReply'), + onTextTooLong: action('onTextTooLong'), + onUseEmoji: action('onUseEmoji'), + preferredReactionEmoji: ['❤️', '👍', '👎', '😂', '😮', '😢'], + renderEmojiPicker: () => <>EmojiPicker, + replies: [], + views: [], + viewTarget: StoryViewTargetType.Views, + onChangeViewTarget: action('onChangeViewTarget'), + }, +} satisfies Meta; function getViewsAndReplies() { const p1 = getDefaultConversation(); @@ -162,7 +147,7 @@ function getViewsAndReplies() { } // eslint-disable-next-line react/function-component-definition -const Template: Story = args => { +const Template: StoryFn = args => { const [, updateArgs] = useArgs(); function onChangeViewTarget(viewTarget: StoryViewTargetType) { diff --git a/ts/components/TextAttachment.stories.tsx b/ts/components/TextAttachment.stories.tsx index 0a2624406b..34a967e999 100644 --- a/ts/components/TextAttachment.stories.tsx +++ b/ts/components/TextAttachment.stories.tsx @@ -3,6 +3,7 @@ import React from 'react'; +import type { Meta } from '@storybook/react'; import enMessages from '../../_locales/en/messages.json'; import { setupI18n } from '../util/setupI18n'; import { TextAttachment } from './TextAttachment'; @@ -17,7 +18,7 @@ const getDefaultProps = (): PropsType => ({ export default { title: 'Components/TextAttachment', -}; +} satisfies Meta; export function SolidBgTextBg(): JSX.Element { return ( @@ -34,10 +35,6 @@ export function SolidBgTextBg(): JSX.Element { ); } -SolidBgTextBg.story = { - name: 'Solid bg + text bg', -}; - export function Gradient(): JSX.Element { return ( ; + +export function Defaults(args: PropsType): JSX.Element { + return ; +} + +export function Long(args: PropsType): JSX.Element { + return ( + + Lorem ipsum dolor sit amet, consectetur adipisicing elit. Debitis deserunt + cupiditate doloribus vitae perspiciatis, eos atque mollitia aliquam quae + aspernatur et iure vero illo veritatis quibusdam maiores laborum. + Inventore, minus. + + ); +} diff --git a/ts/components/ToastAlreadyRequestedToJoin.stories.tsx b/ts/components/ToastAlreadyRequestedToJoin.stories.tsx index b8d61e7335..01f397d33f 100644 --- a/ts/components/ToastAlreadyRequestedToJoin.stories.tsx +++ b/ts/components/ToastAlreadyRequestedToJoin.stories.tsx @@ -3,6 +3,8 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/react'; +import type { PropsType } from './ToastAlreadyRequestedToJoin'; import { ToastAlreadyRequestedToJoin } from './ToastAlreadyRequestedToJoin'; import { setupI18n } from '../util/setupI18n'; @@ -17,12 +19,8 @@ const defaultProps = { export default { title: 'Components/ToastAlreadyRequestedToJoin', -}; +} satisfies Meta; export const _ToastAlreadyRequestedToJoin = (): JSX.Element => ( ); - -_ToastAlreadyRequestedToJoin.story = { - name: 'ToastAlreadyRequestedToJoin', -}; diff --git a/ts/components/ToastAlreadyRequestedToJoin.tsx b/ts/components/ToastAlreadyRequestedToJoin.tsx index 5a38cc2c56..a35af96724 100644 --- a/ts/components/ToastAlreadyRequestedToJoin.tsx +++ b/ts/components/ToastAlreadyRequestedToJoin.tsx @@ -5,7 +5,7 @@ import React from 'react'; import type { LocalizerType } from '../types/Util'; import { Toast } from './Toast'; -type PropsType = { +export type PropsType = { i18n: LocalizerType; onClose: () => unknown; }; diff --git a/ts/components/ToastCaptchaFailed.stories.tsx b/ts/components/ToastCaptchaFailed.stories.tsx index 8cada788fb..7592b59c0a 100644 --- a/ts/components/ToastCaptchaFailed.stories.tsx +++ b/ts/components/ToastCaptchaFailed.stories.tsx @@ -3,8 +3,9 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/react'; +import type { PropsType } from './ToastCaptchaFailed'; import { ToastCaptchaFailed } from './ToastCaptchaFailed'; - import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -17,12 +18,8 @@ const defaultProps = { export default { title: 'Components/ToastCaptchaFailed', -}; +} satisfies Meta; export const _ToastCaptchaFailed = (): JSX.Element => ( ); - -_ToastCaptchaFailed.story = { - name: 'ToastCaptchaFailed', -}; diff --git a/ts/components/ToastCaptchaFailed.tsx b/ts/components/ToastCaptchaFailed.tsx index 907982181d..1fd0dd64e1 100644 --- a/ts/components/ToastCaptchaFailed.tsx +++ b/ts/components/ToastCaptchaFailed.tsx @@ -5,7 +5,7 @@ import React from 'react'; import type { LocalizerType } from '../types/Util'; import { Toast } from './Toast'; -type PropsType = { +export type PropsType = { i18n: LocalizerType; onClose: () => unknown; }; diff --git a/ts/components/ToastCaptchaSolved.stories.tsx b/ts/components/ToastCaptchaSolved.stories.tsx index 8fba3fa66e..838b997899 100644 --- a/ts/components/ToastCaptchaSolved.stories.tsx +++ b/ts/components/ToastCaptchaSolved.stories.tsx @@ -3,8 +3,9 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/react'; +import type { PropsType } from './ToastCaptchaSolved'; import { ToastCaptchaSolved } from './ToastCaptchaSolved'; - import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -17,12 +18,8 @@ const defaultProps = { export default { title: 'Components/ToastCaptchaSolved', -}; +} satisfies Meta; export const _ToastCaptchaSolved = (): JSX.Element => ( ); - -_ToastCaptchaSolved.story = { - name: 'ToastCaptchaSolved', -}; diff --git a/ts/components/ToastCaptchaSolved.tsx b/ts/components/ToastCaptchaSolved.tsx index 3d18dfada6..1b410fb1cc 100644 --- a/ts/components/ToastCaptchaSolved.tsx +++ b/ts/components/ToastCaptchaSolved.tsx @@ -5,7 +5,7 @@ import React from 'react'; import type { LocalizerType } from '../types/Util'; import { Toast } from './Toast'; -type PropsType = { +export type PropsType = { i18n: LocalizerType; onClose: () => unknown; }; diff --git a/ts/components/ToastDebugLogError.stories.tsx b/ts/components/ToastDebugLogError.stories.tsx index 419e8eb928..0197d49d86 100644 --- a/ts/components/ToastDebugLogError.stories.tsx +++ b/ts/components/ToastDebugLogError.stories.tsx @@ -3,8 +3,9 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/react'; +import type { PropsType } from './ToastDebugLogError'; import { ToastDebugLogError } from './ToastDebugLogError'; - import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -17,12 +18,8 @@ const defaultProps = { export default { title: 'Components/ToastDebugLogError', -}; +} satisfies Meta; export const _ToastDebugLogError = (): JSX.Element => ( ); - -_ToastDebugLogError.story = { - name: 'ToastDebugLogError', -}; diff --git a/ts/components/ToastDebugLogError.tsx b/ts/components/ToastDebugLogError.tsx index 10765032d1..c0c7f95a34 100644 --- a/ts/components/ToastDebugLogError.tsx +++ b/ts/components/ToastDebugLogError.tsx @@ -5,7 +5,7 @@ import React from 'react'; import type { LocalizerType } from '../types/Util'; import { Toast } from './Toast'; -type PropsType = { +export type PropsType = { i18n: LocalizerType; onClose: () => unknown; }; diff --git a/ts/components/ToastFileSize.stories.tsx b/ts/components/ToastFileSize.stories.tsx index 4641897d8d..eeca4dbb4d 100644 --- a/ts/components/ToastFileSize.stories.tsx +++ b/ts/components/ToastFileSize.stories.tsx @@ -3,8 +3,9 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/react'; +import type { PropsType } from './ToastFileSize'; import { ToastFileSize } from './ToastFileSize'; - import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -17,12 +18,8 @@ const defaultProps = { export default { title: 'Components/ToastFileSize', -}; +} satisfies Meta; export const _ToastFileSize = (): JSX.Element => ( ); - -_ToastFileSize.story = { - name: 'ToastFileSize', -}; diff --git a/ts/components/ToastFileSize.tsx b/ts/components/ToastFileSize.tsx index 6baf2f26f8..76675693f3 100644 --- a/ts/components/ToastFileSize.tsx +++ b/ts/components/ToastFileSize.tsx @@ -10,7 +10,7 @@ export type ToastPropsType = { units: string; }; -type PropsType = { +export type PropsType = { i18n: LocalizerType; onClose: () => unknown; } & ToastPropsType; diff --git a/ts/components/ToastGroupLinkCopied.stories.tsx b/ts/components/ToastGroupLinkCopied.stories.tsx index 905342faf5..5598c690dc 100644 --- a/ts/components/ToastGroupLinkCopied.stories.tsx +++ b/ts/components/ToastGroupLinkCopied.stories.tsx @@ -3,8 +3,9 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/react'; +import type { PropsType } from './ToastGroupLinkCopied'; import { ToastGroupLinkCopied } from './ToastGroupLinkCopied'; - import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -17,12 +18,8 @@ const defaultProps = { export default { title: 'Components/ToastGroupLinkCopied', -}; +} satisfies Meta; export const _ToastGroupLinkCopied = (): JSX.Element => ( ); - -_ToastGroupLinkCopied.story = { - name: 'ToastGroupLinkCopied', -}; diff --git a/ts/components/ToastGroupLinkCopied.tsx b/ts/components/ToastGroupLinkCopied.tsx index d660ad69d9..e24adfdaf4 100644 --- a/ts/components/ToastGroupLinkCopied.tsx +++ b/ts/components/ToastGroupLinkCopied.tsx @@ -5,7 +5,7 @@ import React from 'react'; import type { LocalizerType } from '../types/Util'; import { Toast } from './Toast'; -type PropsType = { +export type PropsType = { i18n: LocalizerType; onClose: () => unknown; }; diff --git a/ts/components/ToastInternalError.stories.tsx b/ts/components/ToastInternalError.stories.tsx index edc512cdd6..39b27e4d2f 100644 --- a/ts/components/ToastInternalError.stories.tsx +++ b/ts/components/ToastInternalError.stories.tsx @@ -3,11 +3,12 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/react'; +import type { PropsType } from './ToastInternalError'; import { ToastInternalError, ToastInternalErrorKind, } from './ToastInternalError'; - import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -21,7 +22,7 @@ const defaultProps = { export default { title: 'Components/ToastInternalError', -}; +} satisfies Meta; export function ToastDecryptionError(): JSX.Element { return ( @@ -34,10 +35,6 @@ export function ToastDecryptionError(): JSX.Element { ); } -ToastDecryptionError.story = { - name: 'ToastDecryptionError', -}; - export function ToastCDSMirroringError(): JSX.Element { return ( ); } - -ToastDecryptionError.story = { - name: 'ToastCDSMirroringError', -}; diff --git a/ts/components/ToastInternalError.tsx b/ts/components/ToastInternalError.tsx index 4aaf1239f0..d65c3557dc 100644 --- a/ts/components/ToastInternalError.tsx +++ b/ts/components/ToastInternalError.tsx @@ -24,7 +24,7 @@ export type ToastPropsType = { } ); -type PropsType = { +export type PropsType = { i18n: LocalizerType; onClose: () => unknown; } & ToastPropsType; diff --git a/ts/components/ToastLinkCopied.stories.tsx b/ts/components/ToastLinkCopied.stories.tsx index 3cdec4e44b..48f83e16ca 100644 --- a/ts/components/ToastLinkCopied.stories.tsx +++ b/ts/components/ToastLinkCopied.stories.tsx @@ -3,8 +3,9 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/react'; +import type { PropsType } from './ToastLinkCopied'; import { ToastLinkCopied } from './ToastLinkCopied'; - import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -17,12 +18,8 @@ const defaultProps = { export default { title: 'Components/ToastLinkCopied', -}; +} satisfies Meta; export const _ToastLinkCopied = (): JSX.Element => ( ); - -_ToastLinkCopied.story = { - name: 'ToastLinkCopied', -}; diff --git a/ts/components/ToastLinkCopied.tsx b/ts/components/ToastLinkCopied.tsx index cf9fceb57f..d4c30b6516 100644 --- a/ts/components/ToastLinkCopied.tsx +++ b/ts/components/ToastLinkCopied.tsx @@ -5,7 +5,7 @@ import React from 'react'; import type { LocalizerType } from '../types/Util'; import { Toast } from './Toast'; -type PropsType = { +export type PropsType = { i18n: LocalizerType; onClose: () => unknown; }; diff --git a/ts/components/ToastLoadingFullLogs.stories.tsx b/ts/components/ToastLoadingFullLogs.stories.tsx index 04499d47d0..b242468ad9 100644 --- a/ts/components/ToastLoadingFullLogs.stories.tsx +++ b/ts/components/ToastLoadingFullLogs.stories.tsx @@ -3,8 +3,9 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/react'; +import type { PropsType } from './ToastLoadingFullLogs'; import { ToastLoadingFullLogs } from './ToastLoadingFullLogs'; - import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -17,12 +18,8 @@ const defaultProps = { export default { title: 'Components/ToastLoadingFullLogs', -}; +} satisfies Meta; export const _ToastLoadingFullLogs = (): JSX.Element => ( ); - -_ToastLoadingFullLogs.story = { - name: 'ToastLoadingFullLogs', -}; diff --git a/ts/components/ToastLoadingFullLogs.tsx b/ts/components/ToastLoadingFullLogs.tsx index e2b4504c32..bfd6cd081f 100644 --- a/ts/components/ToastLoadingFullLogs.tsx +++ b/ts/components/ToastLoadingFullLogs.tsx @@ -5,7 +5,7 @@ import React from 'react'; import type { LocalizerType } from '../types/Util'; import { Toast } from './Toast'; -type PropsType = { +export type PropsType = { i18n: LocalizerType; onClose: () => unknown; }; diff --git a/ts/components/ToastManager.stories.tsx b/ts/components/ToastManager.stories.tsx index fe39e13885..4efddb6e7a 100644 --- a/ts/components/ToastManager.stories.tsx +++ b/ts/components/ToastManager.stories.tsx @@ -1,9 +1,10 @@ // Copyright 2022 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 React from 'react'; +import { action } from '@storybook/addon-actions'; import enMessages from '../../_locales/en/messages.json'; import { ToastManager } from './ToastManager'; import type { AnyToast } from '../types/Toast'; @@ -142,25 +143,23 @@ export default { title: 'Components/ToastManager', component: ToastManager, argTypes: { - hideToast: { action: true }, - openFileInFolder: { action: true }, - onUndoArchive: { action: true }, - i18n: { - defaultValue: i18n, - }, toastType: { - defaultValue: ToastType.AddingUserToGroup, options: ToastType, control: { type: 'select' }, }, - OS: { - defaultValue: 'macOS', - }, }, -} as Meta; + args: { + hideToast: action('hideToast'), + openFileInFolder: action('openFileInFolder'), + onUndoArchive: action('onUndoArchive'), + i18n, + toastType: ToastType.AddingUserToGroup, + OS: 'macOS', + }, +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => { +const Template: StoryFn = args => { const { toastType, ...rest } = args; return ( <> diff --git a/ts/components/ToastStickerPackInstallFailed.stories.tsx b/ts/components/ToastStickerPackInstallFailed.stories.tsx index 00fdf3e219..09ec721d3c 100644 --- a/ts/components/ToastStickerPackInstallFailed.stories.tsx +++ b/ts/components/ToastStickerPackInstallFailed.stories.tsx @@ -3,8 +3,9 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/react'; +import type { PropsType } from './ToastStickerPackInstallFailed'; import { ToastStickerPackInstallFailed } from './ToastStickerPackInstallFailed'; - import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -17,12 +18,8 @@ const defaultProps = { export default { title: 'Components/ToastStickerPackInstallFailed', -}; +} satisfies Meta; export const _ToastStickerPackInstallFailed = (): JSX.Element => ( ); - -_ToastStickerPackInstallFailed.story = { - name: 'ToastStickerPackInstallFailed', -}; diff --git a/ts/components/ToastStickerPackInstallFailed.tsx b/ts/components/ToastStickerPackInstallFailed.tsx index ac353d1444..e396a684b1 100644 --- a/ts/components/ToastStickerPackInstallFailed.tsx +++ b/ts/components/ToastStickerPackInstallFailed.tsx @@ -5,7 +5,7 @@ import React from 'react'; import type { LocalizerType } from '../types/Util'; import { Toast } from './Toast'; -type PropsType = { +export type PropsType = { i18n: LocalizerType; onClose: () => unknown; }; diff --git a/ts/components/ToastVoiceNoteLimit.stories.tsx b/ts/components/ToastVoiceNoteLimit.stories.tsx index 5f9b2e7711..c3a29f5863 100644 --- a/ts/components/ToastVoiceNoteLimit.stories.tsx +++ b/ts/components/ToastVoiceNoteLimit.stories.tsx @@ -3,8 +3,9 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/react'; +import type { PropsType } from './ToastVoiceNoteLimit'; import { ToastVoiceNoteLimit } from './ToastVoiceNoteLimit'; - import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -17,12 +18,8 @@ const defaultProps = { export default { title: 'Components/ToastVoiceNoteLimit', -}; +} satisfies Meta; export const _ToastVoiceNoteLimit = (): JSX.Element => ( ); - -_ToastVoiceNoteLimit.story = { - name: 'ToastVoiceNoteLimit', -}; diff --git a/ts/components/ToastVoiceNoteLimit.tsx b/ts/components/ToastVoiceNoteLimit.tsx index 2b63e2e6db..a9d1e89dd4 100644 --- a/ts/components/ToastVoiceNoteLimit.tsx +++ b/ts/components/ToastVoiceNoteLimit.tsx @@ -5,7 +5,7 @@ import React from 'react'; import type { LocalizerType } from '../types/Util'; import { Toast } from './Toast'; -type PropsType = { +export type PropsType = { i18n: LocalizerType; onClose: () => unknown; }; diff --git a/ts/components/ToastVoiceNoteMustBeOnlyAttachment.stories.tsx b/ts/components/ToastVoiceNoteMustBeOnlyAttachment.stories.tsx index 4ce5f46e6e..4b71ce8796 100644 --- a/ts/components/ToastVoiceNoteMustBeOnlyAttachment.stories.tsx +++ b/ts/components/ToastVoiceNoteMustBeOnlyAttachment.stories.tsx @@ -3,8 +3,9 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; +import type { Meta } from '@storybook/react'; +import type { PropsType } from './ToastVoiceNoteMustBeOnlyAttachment'; import { ToastVoiceNoteMustBeOnlyAttachment } from './ToastVoiceNoteMustBeOnlyAttachment'; - import { setupI18n } from '../util/setupI18n'; import enMessages from '../../_locales/en/messages.json'; @@ -17,12 +18,8 @@ const defaultProps = { export default { title: 'Components/ToastVoiceNoteMustBeOnlyAttachment', -}; +} satisfies Meta; export const _ToastVoiceNoteMustBeOnlyAttachment = (): JSX.Element => ( ); - -_ToastVoiceNoteMustBeOnlyAttachment.story = { - name: 'ToastVoiceNoteMustBeOnlyAttachment', -}; diff --git a/ts/components/ToastVoiceNoteMustBeOnlyAttachment.tsx b/ts/components/ToastVoiceNoteMustBeOnlyAttachment.tsx index 846598267f..f07f4cf367 100644 --- a/ts/components/ToastVoiceNoteMustBeOnlyAttachment.tsx +++ b/ts/components/ToastVoiceNoteMustBeOnlyAttachment.tsx @@ -5,7 +5,7 @@ import React from 'react'; import type { LocalizerType } from '../types/Util'; import { Toast } from './Toast'; -type PropsType = { +export type PropsType = { i18n: LocalizerType; onClose: () => unknown; }; diff --git a/ts/components/Tooltip.stories.tsx b/ts/components/Tooltip.stories.tsx index 4fb4c8e334..05165dc2fc 100644 --- a/ts/components/Tooltip.stories.tsx +++ b/ts/components/Tooltip.stories.tsx @@ -2,22 +2,32 @@ // 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 './Tooltip'; import { Tooltip, TooltipPlacement } from './Tooltip'; import { Theme } from '../util/theme'; -const createProps = (overrideProps: Partial = {}): PropsType => ({ - content: overrideProps.content || 'Hello World', - direction: select('direction', TooltipPlacement, overrideProps.direction), - sticky: overrideProps.sticky, - theme: overrideProps.theme, -}); - export default { title: 'Components/Tooltip', -}; + argTypes: { + content: { control: { type: 'text' } }, + direction: { + control: { type: 'select' }, + options: Object.values(TooltipPlacement), + }, + sticky: { control: { type: 'boolean' } }, + theme: { + control: { type: 'select' }, + options: Object.keys(Theme), + mappings: Theme, + }, + }, + args: { + content: 'Hello World', + direction: TooltipPlacement.Top, + sticky: false, + }, +} satisfies Meta; const Trigger = ( ); -export const _Top = (): JSX.Element => ( - - {Trigger} - -); - -export const _Right = (): JSX.Element => ( - - {Trigger} - -); - -export const _Bottom = (): JSX.Element => ( - - {Trigger} - -); - -export const _Left = (): JSX.Element => ( - - {Trigger} - -); - -export function Sticky(): JSX.Element { +export function Top(args: PropsType): JSX.Element { return ( - + {Trigger} ); } -export function WithAppliedPopperModifiers(): JSX.Element { +export function Right(args: PropsType): JSX.Element { + return ( + + {Trigger} + + ); +} + +export function Bottom(args: PropsType): JSX.Element { + return ( + + {Trigger} + + ); +} + +export function Left(args: PropsType): JSX.Element { + return ( + + {Trigger} + + ); +} + +export function Sticky(args: PropsType): JSX.Element { + return ( + + {Trigger} + + ); +} + +export function WithAppliedPopperModifiers(args: PropsType): JSX.Element { return ( + {Trigger} ); diff --git a/ts/components/UnsupportedOSDialog.stories.tsx b/ts/components/UnsupportedOSDialog.stories.tsx index 4392215d32..824cefccc0 100644 --- a/ts/components/UnsupportedOSDialog.stories.tsx +++ b/ts/components/UnsupportedOSDialog.stories.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - +import type { Meta } from '@storybook/react'; import { UnsupportedOSDialog } from './UnsupportedOSDialog'; import type { PropsType } from './UnsupportedOSDialog'; import { setupI18n } from '../util/setupI18n'; @@ -57,7 +57,7 @@ const permutations: ReadonlyArray<{ export default { title: 'Components/UnsupportedOSDialog', -}; +} satisfies Meta; export function Iterations(): JSX.Element { return ( diff --git a/ts/components/UsernameLinkModalBody.stories.tsx b/ts/components/UsernameLinkModalBody.stories.tsx index 15d9f6fa4d..a74069e4e9 100644 --- a/ts/components/UsernameLinkModalBody.stories.tsx +++ b/ts/components/UsernameLinkModalBody.stories.tsx @@ -2,8 +2,9 @@ // SPDX-License-Identifier: AGPL-3.0-only import React, { useCallback, useState } 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 { UsernameLinkState } from '../state/ducks/usernameEnums'; import { setupI18n } from '../util/setupI18n'; @@ -21,26 +22,18 @@ export default { component: UsernameLinkModalBody, title: 'Components/UsernameLinkModalBody', argTypes: { - i18n: { - defaultValue: i18n, - }, link: { control: { type: 'text' }, - defaultValue: - 'https://signal.me/#eu/n-AJkmmykrFB7j6UODGndSycxcMdp_v6ppRp9rFu5Ad39q_9Ngi_k9-TARWfT43t', }, username: { control: { type: 'text' }, - defaultValue: 'alice.12', }, usernameLinkState: { control: { type: 'select' }, - defaultValue: UsernameLinkState.Ready, options: [UsernameLinkState.Ready, UsernameLinkState.Updating], }, colorId: { control: { type: 'select' }, - defaultValue: ColorEnum.BLUE, mapping: { blue: ColorEnum.BLUE, white: ColorEnum.WHITE, @@ -52,17 +45,22 @@ export default { purple: ColorEnum.PURPLE, }, }, - showToast: { action: true }, - resetUsernameLink: { action: true }, - setUsernameLinkColor: { action: true }, - onBack: { action: true }, }, -} as Meta; - -type ArgsType = PropsType; + args: { + i18n, + link: 'https://signal.me/#eu/n-AJkmmykrFB7j6UODGndSycxcMdp_v6ppRp9rFu5Ad39q_9Ngi_k9-TARWfT43t', + username: 'alice.12', + usernameLinkState: UsernameLinkState.Ready, + colorId: ColorEnum.BLUE, + showToast: action('showToast'), + resetUsernameLink: action('resetUsernameLink'), + setUsernameLinkColor: action('setUsernameLinkColor'), + onBack: action('onBack'), + }, +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => { +const Template: StoryFn = args => { const [attachment, setAttachment] = useState(); const saveAttachment = useCallback(({ data }: { data?: Uint8Array }) => { if (!data) { @@ -93,13 +91,6 @@ const Template: Story = args => { }; export const Normal = Template.bind({}); -Normal.args = {}; -Normal.story = { - name: 'normal', -}; export const NoLink = Template.bind({}); NoLink.args = { link: '' }; -NoLink.story = { - name: 'normal', -}; diff --git a/ts/components/UsernameOnboardingModalBody.stories.tsx b/ts/components/UsernameOnboardingModalBody.stories.tsx index 4c6b346e1e..ea59722103 100644 --- a/ts/components/UsernameOnboardingModalBody.stories.tsx +++ b/ts/components/UsernameOnboardingModalBody.stories.tsx @@ -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'; @@ -15,23 +16,15 @@ const i18n = setupI18n('en', enMessages); export default { component: UsernameOnboardingModalBody, title: 'Components/UsernameOnboardingModalBody', - argTypes: { - i18n: { - defaultValue: i18n, - }, - onNext: { action: true }, + args: { + i18n, + onNext: action('onNext'), }, -} as Meta; - -type ArgsType = PropsType; +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => { +const Template: StoryFn = args => { return ; }; export const Normal = Template.bind({}); -Normal.args = {}; -Normal.story = { - name: 'normal', -}; diff --git a/ts/components/WhatsNewModal.stories.tsx b/ts/components/WhatsNewModal.stories.tsx index fc24bd9beb..69e7f5b5f3 100644 --- a/ts/components/WhatsNewModal.stories.tsx +++ b/ts/components/WhatsNewModal.stories.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import type { PropsType } from './WhatsNewModal'; import { WhatsNewModal } from './WhatsNewModal'; import enMessages from '../../_locales/en/messages.json'; @@ -13,7 +13,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/WhatsNewModal', -}; +} satisfies Meta; const getDefaultProps = (): PropsType => ({ hideWhatsNewModal: action('hideWhatsNewModal'), diff --git a/ts/components/conversation/AddNewLines.stories.tsx b/ts/components/conversation/AddNewLines.stories.tsx index 07f3fd3181..0f0545f3e5 100644 --- a/ts/components/conversation/AddNewLines.stories.tsx +++ b/ts/components/conversation/AddNewLines.stories.tsx @@ -2,12 +2,13 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; +import type { Meta } from '@storybook/react'; import type { Props } from './AddNewLines'; import { AddNewLines } from './AddNewLines'; export default { title: 'Components/Conversation/AddNewLines', -}; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ renderNonNewLine: overrideProps.renderNonNewLine, @@ -22,10 +23,6 @@ export function AllNewlines(): JSX.Element { return ; } -AllNewlines.story = { - name: 'All newlines', -}; - export function StartingEndingWithNewlines(): JSX.Element { const props = createProps({ text: '\nSome text\n', @@ -34,10 +31,6 @@ export function StartingEndingWithNewlines(): JSX.Element { return ; } -StartingEndingWithNewlines.story = { - name: 'Starting/Ending with Newlines', -}; - export function NewlinesInTheMiddle(): JSX.Element { const props = createProps({ text: 'Some\ntext', @@ -46,10 +39,6 @@ export function NewlinesInTheMiddle(): JSX.Element { return ; } -NewlinesInTheMiddle.story = { - name: 'Newlines in the Middle', -}; - export function NoNewlines(): JSX.Element { const props = createProps({ text: 'Some text', diff --git a/ts/components/conversation/AtMentionify.stories.tsx b/ts/components/conversation/AtMentionify.stories.tsx index 01b99192f0..513ed61e7f 100644 --- a/ts/components/conversation/AtMentionify.stories.tsx +++ b/ts/components/conversation/AtMentionify.stories.tsx @@ -2,9 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import { generateAci } from '../../types/ServiceId'; import type { Props } from './AtMentionify'; import { AtMentionify } from './AtMentionify'; @@ -18,7 +17,7 @@ const SERVICE_ID_6 = generateAci(); export default { title: 'Components/Conversation/AtMentionify', -}; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ mentions: overrideProps.mentions, diff --git a/ts/components/conversation/AttachmentList.stories.tsx b/ts/components/conversation/AttachmentList.stories.tsx index 732e4f296c..050b7f9c4f 100644 --- a/ts/components/conversation/AttachmentList.stories.tsx +++ b/ts/components/conversation/AttachmentList.stories.tsx @@ -2,10 +2,12 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - import { action } from '@storybook/addon-actions'; - -import type { AttachmentDraftType } from '../../types/Attachment'; +import type { Meta } from '@storybook/react'; +import type { + AttachmentDraftType, + AttachmentType, +} from '../../types/Attachment'; import type { Props } from './AttachmentList'; import { AttachmentList } from './AttachmentList'; import { @@ -17,14 +19,13 @@ import { } from '../../types/MIME'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; - import { fakeDraftAttachment } from '../../test-both/helpers/fakeAttachment'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/AttachmentList', -}; +} satisfies Meta>; const createProps = ( overrideProps: Partial> = {} @@ -110,10 +111,6 @@ export function MultipleWithNonVisualTypes(): JSX.Element { return ; } -MultipleWithNonVisualTypes.story = { - name: 'Multiple with Non-Visual Types', -}; - export function EmptyList(): JSX.Element { const props = createProps(); diff --git a/ts/components/conversation/CallingNotification.stories.tsx b/ts/components/conversation/CallingNotification.stories.tsx index 6f9504246d..af184815dd 100644 --- a/ts/components/conversation/CallingNotification.stories.tsx +++ b/ts/components/conversation/CallingNotification.stories.tsx @@ -3,7 +3,7 @@ import * as 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 { CallMode } from '../../types/Calling'; @@ -26,7 +26,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/CallingNotification', -}; +} satisfies Meta; const getCommonProps = (options: { mode: CallMode; @@ -256,10 +256,6 @@ export function TwoIncomingDirectCallsBackToBack(): JSX.Element { ); } -TwoIncomingDirectCallsBackToBack.story = { - name: 'Two incoming direct calls back-to-back', -}; - export function TwoOutgoingDirectCallsBackToBack(): JSX.Element { return ( <> @@ -290,10 +286,6 @@ export function TwoOutgoingDirectCallsBackToBack(): JSX.Element { ); } -TwoOutgoingDirectCallsBackToBack.story = { - name: 'Two outgoing direct calls back-to-back', -}; - export function GroupCallByUnknown(): JSX.Element { return ( ); } - -GroupCallEnded.story = { - name: 'Group call: ended', -}; diff --git a/ts/components/conversation/ChangeNumberNotification.stories.tsx b/ts/components/conversation/ChangeNumberNotification.stories.tsx index a2fa44a74d..97c173d307 100644 --- a/ts/components/conversation/ChangeNumberNotification.stories.tsx +++ b/ts/components/conversation/ChangeNumberNotification.stories.tsx @@ -2,16 +2,16 @@ // 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 { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation'; - +import type { Props } from './ChangeNumberNotification'; import { ChangeNumberNotification } from './ChangeNumberNotification'; export default { title: 'Components/Conversation/ChangeNumberNotification', -}; +} satisfies Meta; const i18n = setupI18n('en', enMessages); @@ -36,7 +36,3 @@ export function LongName(): JSX.Element { /> ); } - -LongName.story = { - name: 'Long name', -}; diff --git a/ts/components/conversation/ChatSessionRefreshedDialog.stories.tsx b/ts/components/conversation/ChatSessionRefreshedDialog.stories.tsx index 43eb24d5ef..9570fa8524 100644 --- a/ts/components/conversation/ChatSessionRefreshedDialog.stories.tsx +++ b/ts/components/conversation/ChatSessionRefreshedDialog.stories.tsx @@ -3,16 +3,17 @@ import * as 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 './ChatSessionRefreshedDialog'; import { ChatSessionRefreshedDialog } from './ChatSessionRefreshedDialog'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ChatSessionRefreshedDialog', -}; +} satisfies Meta; export function Default(): JSX.Element { return ( diff --git a/ts/components/conversation/ChatSessionRefreshedNotification.stories.tsx b/ts/components/conversation/ChatSessionRefreshedNotification.stories.tsx index ebe5884bac..0441e86ced 100644 --- a/ts/components/conversation/ChatSessionRefreshedNotification.stories.tsx +++ b/ts/components/conversation/ChatSessionRefreshedNotification.stories.tsx @@ -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 './ChatSessionRefreshedNotification'; import { ChatSessionRefreshedNotification } from './ChatSessionRefreshedNotification'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ChatSessionRefreshedNotification', -}; +} satisfies Meta; export function Default(): JSX.Element { return ; diff --git a/ts/components/conversation/ContactDetail.stories.tsx b/ts/components/conversation/ContactDetail.stories.tsx index 3c25a2cc93..b1c2af07ea 100644 --- a/ts/components/conversation/ContactDetail.stories.tsx +++ b/ts/components/conversation/ContactDetail.stories.tsx @@ -2,23 +2,21 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import type { Props } from './ContactDetail'; import { ContactDetail } from './ContactDetail'; import { AddressType, ContactFormType } from '../../types/EmbeddedContact'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; import { IMAGE_GIF } from '../../types/MIME'; - import { fakeAttachment } from '../../test-both/helpers/fakeAttachment'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ContactDetail', -}; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ contact: overrideProps.contact || {}, @@ -192,10 +190,6 @@ export function GivenFamilyName(): JSX.Element { return ; } -GivenFamilyName.story = { - name: 'Given + Family Name', -}; - export function FamilyName(): JSX.Element { const props = createProps({ contact: { @@ -232,17 +226,9 @@ export function EmptyWithAccount(): JSX.Element { return ; } -EmptyWithAccount.story = { - name: 'Empty with Account', -}; - export function EmptyWithoutAccount(): JSX.Element { const props = createProps({ hasSignalAccount: false, }); return ; } - -EmptyWithoutAccount.story = { - name: 'Empty without Account', -}; diff --git a/ts/components/conversation/ContactModal.stories.tsx b/ts/components/conversation/ContactModal.stories.tsx index 78d97014ef..a5af37e00e 100644 --- a/ts/components/conversation/ContactModal.stories.tsx +++ b/ts/components/conversation/ContactModal.stories.tsx @@ -1,10 +1,10 @@ // 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 casual from 'casual'; - +import { action } from '@storybook/addon-actions'; import type { ConversationType } from '../../state/ducks/conversations'; import type { PropsType } from './ContactModal'; import enMessages from '../../../_locales/en/messages.json'; @@ -31,65 +31,41 @@ const defaultGroup: ConversationType = getDefaultConversation({ export default { title: 'Components/Conversation/ContactModal', component: ContactModal, - argTypes: { - i18n: { - defaultValue: i18n, - }, - areWeASubscriber: { - defaultValue: false, - }, - areWeAdmin: { - defaultValue: false, - }, - badges: { - defaultValue: [], - }, - contact: { - defaultValue: defaultContact, - }, - conversation: { - defaultValue: defaultGroup, - }, - hasStories: { - defaultValue: undefined, - }, - hideContactModal: { action: true }, - isAdmin: { - defaultValue: false, - }, - isMember: { - defaultValue: true, - }, - removeMemberFromGroup: { action: true }, - showConversation: { action: true }, - theme: { - defaultValue: ThemeType.light, - }, - toggleAdmin: { action: true }, - toggleSafetyNumberModal: { action: true }, - updateConversationModelSharedGroups: { action: true }, - viewUserStories: { action: true }, + args: { + i18n, + areWeASubscriber: false, + areWeAdmin: false, + badges: [], + contact: defaultContact, + conversation: defaultGroup, + hasStories: undefined, + hideContactModal: action('hideContactModal'), + isAdmin: false, + isMember: true, + removeMemberFromGroup: action('removeMemberFromGroup'), + showConversation: action('showConversation'), + theme: ThemeType.light, + toggleAdmin: action('toggleAdmin'), + toggleSafetyNumberModal: action('toggleSafetyNumberModal'), + updateConversationModelSharedGroups: action( + 'updateConversationModelSharedGroups' + ), + viewUserStories: action('viewUserStories'), }, -} as Meta; +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; +const Template: StoryFn = args => ; export const AsNonAdmin = Template.bind({}); AsNonAdmin.args = { areWeAdmin: false, }; -AsNonAdmin.story = { - name: 'As non-admin', -}; export const AsAdmin = Template.bind({}); AsAdmin.args = { areWeAdmin: true, }; -AsAdmin.story = { - name: 'As admin', -}; export const AsAdminWithNoGroupLink = Template.bind({}); AsAdminWithNoGroupLink.args = { @@ -99,17 +75,11 @@ AsAdminWithNoGroupLink.args = { groupLink: undefined, }, }; -AsAdminWithNoGroupLink.story = { - name: 'As admin with no group link', -}; export const AsAdminViewingNonMemberOfGroup = Template.bind({}); AsAdminViewingNonMemberOfGroup.args = { isMember: false, }; -AsAdminViewingNonMemberOfGroup.story = { - name: 'As admin, viewing non-member of group', -}; export const WithoutPhoneNumber = Template.bind({}); WithoutPhoneNumber.args = { @@ -118,9 +88,6 @@ WithoutPhoneNumber.args = { phoneNumber: undefined, }, }; -WithoutPhoneNumber.story = { - name: 'Without phone number', -}; export const ViewingSelf = Template.bind({}); ViewingSelf.args = { @@ -129,17 +96,11 @@ ViewingSelf.args = { isMe: true, }, }; -ViewingSelf.story = { - name: 'Viewing self', -}; export const WithBadges = Template.bind({}); WithBadges.args = { badges: getFakeBadges(2), }; -WithBadges.story = { - name: 'With badges', -}; export const WithUnreadStories = Template.bind({}); WithUnreadStories.args = { diff --git a/ts/components/conversation/ContactName.stories.tsx b/ts/components/conversation/ContactName.stories.tsx index 8ce5763c62..33ce1d3f5b 100644 --- a/ts/components/conversation/ContactName.stories.tsx +++ b/ts/components/conversation/ContactName.stories.tsx @@ -2,22 +2,19 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - +import type { Meta } from '@storybook/react'; +import type { PropsType } from './ContactName'; import { ContactName } from './ContactName'; import { ContactNameColors } from '../../types/Colors'; export default { title: 'Components/Conversation/ContactName', -}; +} satisfies Meta; export function FirstNameAndTitleTitlePreferred(): JSX.Element { return ; } -FirstNameAndTitleTitlePreferred.story = { - name: 'First name and title; title preferred', -}; - export function FirstNameAndTitleFirstNamePreferred(): JSX.Element { return ( diff --git a/ts/components/conversation/ContactSpoofingReviewDialog.stories.tsx b/ts/components/conversation/ContactSpoofingReviewDialog.stories.tsx index f9cc4e7fcb..77533746bb 100644 --- a/ts/components/conversation/ContactSpoofingReviewDialog.stories.tsx +++ b/ts/components/conversation/ContactSpoofingReviewDialog.stories.tsx @@ -4,10 +4,11 @@ import React from 'react'; 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 { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation'; - +import type { PropsType } from './ContactSpoofingReviewDialog'; import { ContactSpoofingReviewDialog } from './ContactSpoofingReviewDialog'; import { ContactSpoofingType } from '../../util/contactSpoofing'; import { ThemeType } from '../../types/Util'; @@ -16,7 +17,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ContactSpoofingReviewDialog', -}; +} satisfies Meta; const getCommonProps = () => ({ acceptConversation: action('acceptConversation'), @@ -44,10 +45,6 @@ export function DirectConversationsWithSameTitle(): JSX.Element { ); } -DirectConversationsWithSameTitle.story = { - name: 'Direct conversations with same title', -}; - export function NotAdmin(): JSX.Element { return ( ); } - -Admin.story = { - name: 'Group conversation many group members, and we are admin', -}; diff --git a/ts/components/conversation/ConversationHeader.stories.tsx b/ts/components/conversation/ConversationHeader.stories.tsx index 18c72b8ce2..0bc14f34b5 100644 --- a/ts/components/conversation/ConversationHeader.stories.tsx +++ b/ts/components/conversation/ConversationHeader.stories.tsx @@ -3,15 +3,15 @@ import type { ComponentProps } from 'react'; import React, { useContext } from 'react'; - import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation'; import { getRandomColor } from '../../test-both/helpers/getRandomColor'; import { setupI18n } from '../../util/setupI18n'; import { DurationInSeconds } from '../../util/durations'; import enMessages from '../../../_locales/en/messages.json'; import { StorybookThemeContext } from '../../../.storybook/StorybookThemeContext'; +import type { PropsType } from './ConversationHeader'; import { ConversationHeader, OutgoingCallButtonStyle, @@ -20,7 +20,7 @@ import { gifUrl } from '../../storybook/Fixtures'; export default { title: 'Components/Conversation/ConversationHeader', -}; +} satisfies Meta; const i18n = setupI18n('en', enMessages); @@ -216,10 +216,6 @@ export function PrivateConvo(): JSX.Element { ); } -PrivateConvo.story = { - name: '1:1 conversation', -}; - export function Group(): JSX.Element { const items: ItemsType = [ { @@ -302,11 +298,7 @@ export function Group(): JSX.Element { ); } -Group.story = { - name: 'In a group', -}; - -function NoteToSelf() { +export function NoteToSelf(): JSX.Element { const items: ItemsType = [ { title: 'In chat with yourself', @@ -340,11 +332,7 @@ function NoteToSelf() { ); } -NoteToSelf.story = { - name: 'Note to Self', -}; - -function Unaccepted() { +export function Unaccepted(): JSX.Element { const items: ItemsType = [ { title: '1:1 conversation', @@ -377,7 +365,3 @@ function Unaccepted() { ); } - -Unaccepted.story = { - name: 'Unaccepted', -}; diff --git a/ts/components/conversation/ConversationHero.stories.tsx b/ts/components/conversation/ConversationHero.stories.tsx index fb763928da..b578a473f2 100644 --- a/ts/components/conversation/ConversationHero.stories.tsx +++ b/ts/components/conversation/ConversationHero.stories.tsx @@ -1,10 +1,10 @@ // 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 React, { useContext } from 'react'; import casual from 'casual'; - +import { action } from '@storybook/addon-actions'; import enMessages from '../../../_locales/en/messages.json'; import type { Props } from './ConversationHero'; import { ConversationHero } from './ConversationHero'; @@ -19,24 +19,18 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ConversationHero', component: ConversationHero, - argTypes: { - conversationType: { - defaultValue: 'direct', - }, - i18n: { - defaultValue: i18n, - }, - theme: { - defaultValue: ThemeType.light, - }, - unblurAvatar: { action: true }, - updateSharedGroups: { action: true }, - viewUserStories: { action: true }, + args: { + conversationType: 'direct', + i18n, + theme: ThemeType.light, + unblurAvatar: action('unblurAvatar'), + updateSharedGroups: action('updateSharedGroups'), + viewUserStories: action('viewUserStories'), }, -} as Meta; +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => { +const Template: StoryFn = args => { const theme = useContext(StorybookThemeContext); return (
@@ -49,57 +43,36 @@ export const DirectFiveOtherGroups = Template.bind({}); DirectFiveOtherGroups.args = { sharedGroupNames: Array.from(Array(5), () => casual.title), }; -DirectFiveOtherGroups.story = { - name: 'Direct (Five Other Groups)', -}; export const DirectFourOtherGroups = Template.bind({}); DirectFourOtherGroups.args = { sharedGroupNames: Array.from(Array(4), () => casual.title), }; -DirectFourOtherGroups.story = { - name: 'Direct (Four Other Groups)', -}; export const DirectThreeOtherGroups = Template.bind({}); DirectThreeOtherGroups.args = { sharedGroupNames: Array.from(Array(3), () => casual.title), }; -DirectThreeOtherGroups.story = { - name: 'Direct (Three Other Groups)', -}; export const DirectTwoOtherGroups = Template.bind({}); DirectTwoOtherGroups.args = { sharedGroupNames: Array.from(Array(2), () => casual.title), }; -DirectTwoOtherGroups.story = { - name: 'Direct (Two Other Groups)', -}; export const DirectOneOtherGroup = Template.bind({}); DirectOneOtherGroup.args = { sharedGroupNames: [casual.title], }; -DirectOneOtherGroup.story = { - name: 'Direct (One Other Group)', -}; export const DirectNoGroupsName = Template.bind({}); DirectNoGroupsName.args = { about: '👍 Free to chat', }; -DirectNoGroupsName.story = { - name: 'Direct (No Groups, Name)', -}; export const DirectNoGroupsJustProfile = Template.bind({}); DirectNoGroupsJustProfile.args = { phoneNumber: casual.phone, }; -DirectNoGroupsJustProfile.story = { - name: 'Direct (No Groups, Just Profile)', -}; export const DirectNoGroupsJustPhoneNumber = Template.bind({}); DirectNoGroupsJustPhoneNumber.args = { @@ -107,9 +80,6 @@ DirectNoGroupsJustPhoneNumber.args = { profileName: '', title: '', }; -DirectNoGroupsJustPhoneNumber.story = { - name: 'Direct (No Groups, Just Phone Number)', -}; export const DirectNoGroupsNoData = Template.bind({}); DirectNoGroupsNoData.args = { @@ -118,9 +88,6 @@ DirectNoGroupsNoData.args = { profileName: '', title: '', }; -DirectNoGroupsNoData.story = { - name: 'Direct (No Groups, No Data)', -}; export const DirectNoGroupsNoDataNotAccepted = Template.bind({}); DirectNoGroupsNoDataNotAccepted.args = { @@ -130,9 +97,6 @@ DirectNoGroupsNoDataNotAccepted.args = { profileName: '', title: '', }; -DirectNoGroupsNoDataNotAccepted.story = { - name: 'Direct (No Groups, No Data, Not Accepted)', -}; export const DirectNoGroupsNotAcceptedWithAvatar = Template.bind({}); DirectNoGroupsNotAcceptedWithAvatar.args = { @@ -140,9 +104,6 @@ DirectNoGroupsNotAcceptedWithAvatar.args = { acceptedMessageRequest: false, profileName: '', }; -DirectNoGroupsNotAcceptedWithAvatar.story = { - name: 'Direct (No Groups, No Data, Not Accepted, With Avatar)', -}; export const GroupManyMembers = Template.bind({}); GroupManyMembers.args = { @@ -151,9 +112,6 @@ GroupManyMembers.args = { membersCount: casual.integer(20, 100), title: casual.title, }; -GroupManyMembers.story = { - name: 'Group (many members)', -}; export const GroupOneMember = Template.bind({}); GroupOneMember.args = { @@ -163,9 +121,6 @@ GroupOneMember.args = { membersCount: 1, title: casual.title, }; -GroupOneMember.story = { - name: 'Group (one member)', -}; export const GroupZeroMembers = Template.bind({}); GroupZeroMembers.args = { @@ -175,9 +130,6 @@ GroupZeroMembers.args = { membersCount: 0, title: casual.title, }; -GroupZeroMembers.story = { - name: 'Group (zero members)', -}; export const GroupLongGroupDescription = Template.bind({}); GroupLongGroupDescription.args = { @@ -187,9 +139,6 @@ GroupLongGroupDescription.args = { membersCount: casual.integer(1, 10), title: casual.title, }; -GroupLongGroupDescription.story = { - name: 'Group (long group description)', -}; export const GroupNoName = Template.bind({}); GroupNoName.args = { @@ -197,17 +146,11 @@ GroupNoName.args = { membersCount: 0, title: '', }; -GroupNoName.story = { - name: 'Group (No name)', -}; export const NoteToSelf = Template.bind({}); NoteToSelf.args = { isMe: true, }; -NoteToSelf.story = { - name: 'Note to Self', -}; export const UnreadStories = Template.bind({}); UnreadStories.args = { diff --git a/ts/components/conversation/ConversationMergeNotification.stories.tsx b/ts/components/conversation/ConversationMergeNotification.stories.tsx index e32a6d6368..1aa6a6db56 100644 --- a/ts/components/conversation/ConversationMergeNotification.stories.tsx +++ b/ts/components/conversation/ConversationMergeNotification.stories.tsx @@ -2,7 +2,7 @@ // 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 './ConversationMergeNotification'; @@ -12,7 +12,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ConversationMergeNotification', -}; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): PropsType => ({ i18n, diff --git a/ts/components/conversation/DeliveryIssueDialog.stories.tsx b/ts/components/conversation/DeliveryIssueDialog.stories.tsx index 4a69f43292..c0a3a2d03b 100644 --- a/ts/components/conversation/DeliveryIssueDialog.stories.tsx +++ b/ts/components/conversation/DeliveryIssueDialog.stories.tsx @@ -3,9 +3,10 @@ import * as 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 './DeliveryIssueDialog'; import { DeliveryIssueDialog } from './DeliveryIssueDialog'; import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation'; @@ -14,7 +15,7 @@ const sender = getDefaultConversation(); export default { title: 'Components/Conversation/DeliveryIssueDialog', -}; +} satisfies Meta; export function Default(): JSX.Element { return ( diff --git a/ts/components/conversation/DeliveryIssueNotification.stories.tsx b/ts/components/conversation/DeliveryIssueNotification.stories.tsx index 9cfeb5d4cc..6bf736cddb 100644 --- a/ts/components/conversation/DeliveryIssueNotification.stories.tsx +++ b/ts/components/conversation/DeliveryIssueNotification.stories.tsx @@ -2,15 +2,16 @@ // 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 './DeliveryIssueNotification'; import { DeliveryIssueNotification } from './DeliveryIssueNotification'; import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation'; export default { title: 'Components/Conversation/DeliveryIssueNotification', -}; +} satisfies Meta; const i18n = setupI18n('en', enMessages); const sender = getDefaultConversation(); @@ -37,10 +38,6 @@ export function WithALongName(): JSX.Element { ); } -WithALongName.story = { - name: 'With a long name', -}; - export function InGroup(): JSX.Element { return ; } diff --git a/ts/components/conversation/Emojify.stories.tsx b/ts/components/conversation/Emojify.stories.tsx index 1afbb2a993..106bd8dca3 100644 --- a/ts/components/conversation/Emojify.stories.tsx +++ b/ts/components/conversation/Emojify.stories.tsx @@ -2,12 +2,13 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; +import type { Meta } from '@storybook/react'; import type { Props } from './Emojify'; import { Emojify } from './Emojify'; export default { title: 'Components/Conversation/Emojify', -}; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ renderNonEmoji: overrideProps.renderNonEmoji, @@ -92,10 +93,6 @@ export function AllTextNoEmoji(): JSX.Element { return ; } -AllTextNoEmoji.story = { - name: 'All Text, No Emoji', -}; - export function CustomTextRender(): JSX.Element { const props = createProps({ text: 'this 😹 cat 😹 is 😹 so 😹 joyful', @@ -117,10 +114,6 @@ export function TensOfThousandsOfEmoji(): JSX.Element { return ; } -TensOfThousandsOfEmoji.story = { - name: 'Tens of thousands of emoji', -}; - export function TensOfThousandsOfEmojiInterspersedWithText(): JSX.Element { const props = createProps({ text: '💅 hi '.repeat(40000), @@ -128,7 +121,3 @@ export function TensOfThousandsOfEmojiInterspersedWithText(): JSX.Element { return ; } - -TensOfThousandsOfEmojiInterspersedWithText.story = { - name: 'Tens of thousands of emoji, interspersed with text', -}; diff --git a/ts/components/conversation/ErrorBoundary.stories.tsx b/ts/components/conversation/ErrorBoundary.stories.tsx index b3b24b9075..8e574ae521 100644 --- a/ts/components/conversation/ErrorBoundary.stories.tsx +++ b/ts/components/conversation/ErrorBoundary.stories.tsx @@ -3,16 +3,17 @@ import * as 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 { Props } from './ErrorBoundary'; import { ErrorBoundary } from './ErrorBoundary'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ErrorBoundary', -}; +} satisfies Meta; const Fail: React.FC> = () => { throw new Error('Failed'); @@ -25,7 +26,3 @@ export function ErrorState(): JSX.Element { ); } - -ErrorState.story = { - name: 'Error state', -}; diff --git a/ts/components/conversation/ExpireTimer.stories.tsx b/ts/components/conversation/ExpireTimer.stories.tsx index fd5283d6c5..87c0b63469 100644 --- a/ts/components/conversation/ExpireTimer.stories.tsx +++ b/ts/components/conversation/ExpireTimer.stories.tsx @@ -2,12 +2,13 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; +import type { Meta } from '@storybook/react'; import type { Props } from './ExpireTimer'; import { ExpireTimer } from './ExpireTimer'; export default { title: 'Components/Conversation/ExpireTimer', -}; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ direction: overrideProps.direction || 'outgoing', @@ -25,10 +26,6 @@ export const _30Seconds = (): JSX.Element => { return ; }; -_30Seconds.story = { - name: '30 seconds', -}; - export const _2Minutes = (): JSX.Element => { const twoMinutes = 60 * 1000 * 2; const props = createProps({ @@ -39,10 +36,6 @@ export const _2Minutes = (): JSX.Element => { return ; }; -_2Minutes.story = { - name: '2 minutes', -}; - export function InProgress(): JSX.Element { const props = createProps({ expirationTimestamp: Date.now() + 15 * 1000, diff --git a/ts/components/conversation/GroupDescription.stories.tsx b/ts/components/conversation/GroupDescription.stories.tsx index 45b4510ec2..01dd57e71a 100644 --- a/ts/components/conversation/GroupDescription.stories.tsx +++ b/ts/components/conversation/GroupDescription.stories.tsx @@ -2,9 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import React from 'react'; - -import { text } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import type { PropsType } from './GroupDescription'; import { GroupDescription } from './GroupDescription'; import { setupI18n } from '../../util/setupI18n'; @@ -14,80 +12,57 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/GroupDescription', -}; + argTypes: { + title: { control: { type: 'text' } }, + text: { control: { type: 'text' } }, + }, + args: { + i18n, + title: 'Sample Title', + text: 'Default group description', + }, +} satisfies Meta; -const createProps = (overrideProps: Partial = {}): PropsType => ({ - i18n, - title: text('title', overrideProps.title || 'Sample Title'), - text: text('text', overrideProps.text || 'Default group description'), -}); - -export function Default(): JSX.Element { - return ; +export function Default(args: PropsType): JSX.Element { + return ; } -export function Long(): JSX.Element { +export function Long(args: PropsType): JSX.Element { return ( ); } -export function WithNewlines(): JSX.Element { +export function WithNewlines(args: PropsType): JSX.Element { return ( ); } -WithNewlines.story = { - name: 'With newlines', -}; +export function WithEmoji(args: PropsType): JSX.Element { + return ; +} -export function WithEmoji(): JSX.Element { +export function WithLink(args: PropsType): JSX.Element { return ( ); } -WithEmoji.story = { - name: 'With emoji', -}; - -export function WithLink(): JSX.Element { +export function KitchenSink(args: PropsType): JSX.Element { return ( ); } - -WithLink.story = { - name: 'With link', -}; - -export function KitchenSink(): JSX.Element { - return ( - - ); -} - -KitchenSink.story = { - name: 'Kitchen sink', -}; diff --git a/ts/components/conversation/GroupNotification.stories.tsx b/ts/components/conversation/GroupNotification.stories.tsx index 2f6eaf83a7..e90d2e6c9d 100644 --- a/ts/components/conversation/GroupNotification.stories.tsx +++ b/ts/components/conversation/GroupNotification.stories.tsx @@ -2,7 +2,7 @@ // 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 { Props } from './GroupNotification'; @@ -11,7 +11,7 @@ import { getDefaultConversation } from '../../test-both/helpers/getDefaultConver export default { title: 'Components/Conversation', -}; +} satisfies Meta; const i18n = setupI18n('en', enMessages); @@ -394,7 +394,3 @@ export const _GroupNotification = (): JSX.Element => ( ))} ); - -_GroupNotification.story = { - name: 'GroupNotification', -}; diff --git a/ts/components/conversation/GroupV1DisabledActions.stories.tsx b/ts/components/conversation/GroupV1DisabledActions.stories.tsx index f518d76547..39a098d050 100644 --- a/ts/components/conversation/GroupV1DisabledActions.stories.tsx +++ b/ts/components/conversation/GroupV1DisabledActions.stories.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import type { PropsType as GroupV1DisabledActionsPropsType } from './GroupV1DisabledActions'; import { GroupV1DisabledActions } from './GroupV1DisabledActions'; import { setupI18n } from '../../util/setupI18n'; @@ -19,7 +19,7 @@ const createProps = (): GroupV1DisabledActionsPropsType => ({ export default { title: 'Components/Conversation/GroupV1DisabledActions', -}; +} satisfies Meta; export function Default(): JSX.Element { return ; diff --git a/ts/components/conversation/GroupV1Migration.stories.tsx b/ts/components/conversation/GroupV1Migration.stories.tsx index 17a699cf30..294d46713f 100644 --- a/ts/components/conversation/GroupV1Migration.stories.tsx +++ b/ts/components/conversation/GroupV1Migration.stories.tsx @@ -2,9 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; -import { isBoolean } from 'lodash'; -import { boolean } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; @@ -26,101 +24,62 @@ const contact2 = getDefaultConversation({ id: 'guid-2', }); -const createProps = (overrideProps: Partial = {}): PropsType => ({ - areWeInvited: boolean( - 'areWeInvited', - isBoolean(overrideProps.areWeInvited) ? overrideProps.areWeInvited : false - ), - conversationId: '123', - droppedMembers: overrideProps.droppedMembers || [contact1], - getPreferredBadge: () => undefined, - i18n, - invitedMembers: overrideProps.invitedMembers || [contact2], - theme: ThemeType.light, -}); - export default { title: 'Components/Conversation/GroupV1Migration', -}; + argTypes: { + areWeInvited: { control: { type: 'boolean' } }, + }, + args: { + areWeInvited: false, + conversationId: '123', + droppedMembers: [contact1], + getPreferredBadge: () => undefined, + i18n, + invitedMembers: [contact2], + theme: ThemeType.light, + }, +} satisfies Meta; -export function YouWereInvited(): JSX.Element { +export function YouWereInvited(args: PropsType): JSX.Element { + return ; +} + +export function SingleDroppedAndSingleInvitedMember( + args: PropsType +): JSX.Element { + return ; +} + +export function MultipleDroppedAndInvitedMembers(args: PropsType): JSX.Element { return ( ); } -YouWereInvited.story = { - name: 'You were invited', -}; - -export function SingleDroppedAndSingleInvitedMember(): JSX.Element { - return ; -} - -SingleDroppedAndSingleInvitedMember.story = { - name: 'Single dropped and single invited member', -}; - -export function MultipleDroppedAndInvitedMembers(): JSX.Element { +export function JustInvitedMembers(args: PropsType): JSX.Element { return ( ); } -MultipleDroppedAndInvitedMembers.story = { - name: 'Multiple dropped and invited members', -}; - -export function JustInvitedMembers(): JSX.Element { +export function JustDroppedMembers(args: PropsType): JSX.Element { return ( ); } -JustInvitedMembers.story = { - name: 'Just invited members', -}; - -export function JustDroppedMembers(): JSX.Element { - return ( - - ); +export function NoDroppedOrInvitedMembers(args: PropsType): JSX.Element { + return ; } - -JustDroppedMembers.story = { - name: 'Just dropped members', -}; - -export function NoDroppedOrInvitedMembers(): JSX.Element { - return ( - - ); -} - -NoDroppedOrInvitedMembers.story = { - name: 'No dropped or invited members', -}; diff --git a/ts/components/conversation/GroupV2Change.stories.tsx b/ts/components/conversation/GroupV2Change.stories.tsx index 07db88565b..00657461d8 100644 --- a/ts/components/conversation/GroupV2Change.stories.tsx +++ b/ts/components/conversation/GroupV2Change.stories.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import { setupI18n } from '../../util/setupI18n'; import { generateAci, generatePni } from '../../types/ServiceId'; import type { ServiceIdString, AciString } from '../../types/ServiceId'; @@ -11,6 +11,7 @@ import enMessages from '../../../_locales/en/messages.json'; import type { GroupV2ChangeType } from '../../groups'; import { SignalService as Proto } from '../../protobuf'; import type { SmartContactRendererType } from '../../groupChange'; +import type { PropsType } from './GroupV2Change'; import { GroupV2Change } from './GroupV2Change'; import type { FullJSXType } from '../Intl'; @@ -69,7 +70,7 @@ const renderChange = ( export default { title: 'Components/Conversation/GroupV2Change', -}; +} satisfies Meta; export function Multiple(): JSX.Element { return ( @@ -313,10 +314,6 @@ export function AccessAttributes(): JSX.Element { ); } -AccessAttributes.story = { - name: 'Access (Attributes)', -}; - export function AccessMembers(): JSX.Element { return ( <> @@ -376,10 +373,6 @@ export function AccessMembers(): JSX.Element { ); } -AccessMembers.story = { - name: 'Access (Members)', -}; - export function AccessInviteLink(): JSX.Element { return ( <> @@ -439,10 +432,6 @@ export function AccessInviteLink(): JSX.Element { ); } -AccessInviteLink.story = { - name: 'Access (Invite Link)', -}; - export function MemberAdd(): JSX.Element { return ( <> @@ -619,10 +608,6 @@ export function MemberAddFromInvited(): JSX.Element { ); } -MemberAddFromInvited.story = { - name: 'Member Add (from invited)', -}; - export function MemberAddFromLink(): JSX.Element { return ( <> @@ -656,10 +641,6 @@ export function MemberAddFromLink(): JSX.Element { ); } -MemberAddFromLink.story = { - name: 'Member Add (from link)', -}; - export function MemberAddFromAdminApproval(): JSX.Element { return ( <> @@ -710,10 +691,6 @@ export function MemberAddFromAdminApproval(): JSX.Element { ); } -MemberAddFromAdminApproval.story = { - name: 'Member Add (from admin approval)', -}; - export function MemberRemove(): JSX.Element { return ( <> @@ -935,10 +912,6 @@ export function PendingAddOne(): JSX.Element { ); } -PendingAddOne.story = { - name: 'Pending Add - one', -}; - export function PendingAddMany(): JSX.Element { return ( <> @@ -972,10 +945,6 @@ export function PendingAddMany(): JSX.Element { ); } -PendingAddMany.story = { - name: 'Pending Add - many', -}; - export function PendingRemoveOne(): JSX.Element { return ( <> @@ -1119,10 +1088,6 @@ export function PendingRemoveOne(): JSX.Element { ); } -PendingRemoveOne.story = { - name: 'Pending Remove - one', -}; - export function PendingRemoveMany(): JSX.Element { return ( <> @@ -1215,10 +1180,6 @@ export function PendingRemoveMany(): JSX.Element { ); } -PendingRemoveMany.story = { - name: 'Pending Remove - many', -}; - export function AdminApprovalAdd(): JSX.Element { return ( <> @@ -1242,10 +1203,6 @@ export function AdminApprovalAdd(): JSX.Element { ); } -AdminApprovalAdd.story = { - name: 'Admin Approval (Add)', -}; - export function AdminApprovalRemove(): JSX.Element { return ( <> @@ -1279,10 +1236,6 @@ export function AdminApprovalRemove(): JSX.Element { ); } -AdminApprovalRemove.story = { - name: 'Admin Approval (Remove)', -}; - export function AdminApprovalBounce(): JSX.Element { return ( <> @@ -1384,10 +1337,6 @@ export function AdminApprovalBounce(): JSX.Element { ); } -AdminApprovalBounce.story = { - name: 'Admin Approval (Bounce)', -}; - export function GroupLinkAdd(): JSX.Element { return ( <> @@ -1447,10 +1396,6 @@ export function GroupLinkAdd(): JSX.Element { ); } -GroupLinkAdd.story = { - name: 'Group Link (Add)', -}; - export function GroupLinkReset(): JSX.Element { return ( <> @@ -1481,10 +1426,6 @@ export function GroupLinkReset(): JSX.Element { ); } -GroupLinkReset.story = { - name: 'Group Link (Reset)', -}; - export function GroupLinkRemove(): JSX.Element { return ( <> @@ -1515,10 +1456,6 @@ export function GroupLinkRemove(): JSX.Element { ); } -GroupLinkRemove.story = { - name: 'Group Link (Remove)', -}; - export function DescriptionRemove(): JSX.Element { return ( <> @@ -1552,10 +1489,6 @@ export function DescriptionRemove(): JSX.Element { ); } -DescriptionRemove.story = { - name: 'Description (Remove)', -}; - export function DescriptionChange(): JSX.Element { return ( <> @@ -1601,10 +1534,6 @@ export function DescriptionChange(): JSX.Element { ); } -DescriptionChange.story = { - name: 'Description (Change)', -}; - export function AnnouncementGroupChange(): JSX.Element { return ( <> @@ -1664,10 +1593,6 @@ export function AnnouncementGroupChange(): JSX.Element { ); } -AnnouncementGroupChange.story = { - name: 'Announcement Group (Change)', -}; - export function Summary(): JSX.Element { return ( <> diff --git a/ts/components/conversation/GroupV2PendingApprovalActions.stories.tsx b/ts/components/conversation/GroupV2PendingApprovalActions.stories.tsx index 1a4e0807e2..caa5799ae4 100644 --- a/ts/components/conversation/GroupV2PendingApprovalActions.stories.tsx +++ b/ts/components/conversation/GroupV2PendingApprovalActions.stories.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import type { PropsType as GroupV2PendingApprovalActionsPropsType } from './GroupV2PendingApprovalActions'; import { GroupV2PendingApprovalActions } from './GroupV2PendingApprovalActions'; import { setupI18n } from '../../util/setupI18n'; @@ -19,7 +19,7 @@ const createProps = (): GroupV2PendingApprovalActionsPropsType => ({ export default { title: 'Components/Conversation/GroupV2PendingApprovalActions', -}; +} satisfies Meta; export function Default(): JSX.Element { return ; diff --git a/ts/components/conversation/Image.stories.tsx b/ts/components/conversation/Image.stories.tsx index b5aa554c3b..5729d5971a 100644 --- a/ts/components/conversation/Image.stories.tsx +++ b/ts/components/conversation/Image.stories.tsx @@ -2,9 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import { pngUrl } from '../../storybook/Fixtures'; import type { Props } from './Image'; import { CurveType, Image } from './Image'; @@ -20,7 +19,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/Image', -}; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ alt: overrideProps.alt || '', @@ -60,10 +59,6 @@ export function UrlWithHeightWidth(): JSX.Element { return ; } -UrlWithHeightWidth.story = { - name: 'URL with Height/Width', -}; - export function Caption(): JSX.Element { const defaultProps = createProps(); const props = { @@ -112,10 +107,6 @@ export function NoBorderOrBackground(): JSX.Element { ); } -NoBorderOrBackground.story = { - name: 'No Border or Background', -}; - export function Pending(): JSX.Element { const props = createProps({ attachment: fakeAttachment({ @@ -149,10 +140,6 @@ export function PendingWBlurhash(): JSX.Element { ); } -PendingWBlurhash.story = { - name: 'Pending w/blurhash', -}; - export function CurvedCorners(): JSX.Element { const props = createProps({ curveBottomLeft: CurveType.Normal, @@ -200,10 +187,6 @@ export function FullOverlayWithText(): JSX.Element { return ; } -FullOverlayWithText.story = { - name: 'Full Overlay with Text', -}; - export function Blurhash(): JSX.Element { const defaultProps = createProps(); const props = { @@ -229,10 +212,6 @@ export function UndefinedBlurHash(): JSX.Element { return ; } -UndefinedBlurHash.story = { - name: 'undefined blurHash', -}; - export function MissingImage(): JSX.Element { const defaultProps = createProps(); const props = { diff --git a/ts/components/conversation/ImageGrid.stories.tsx b/ts/components/conversation/ImageGrid.stories.tsx index 9f1eda49fb..5b890fee50 100644 --- a/ts/components/conversation/ImageGrid.stories.tsx +++ b/ts/components/conversation/ImageGrid.stories.tsx @@ -2,10 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - import { action } from '@storybook/addon-actions'; -import { boolean, number } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import type { Props } from './ImageGrid'; import { ImageGrid } from './ImageGrid'; import { @@ -25,44 +23,14 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ImageGrid', -}; - -const createProps = (overrideProps: Partial = {}): Props => ({ - attachments: overrideProps.attachments || [ - fakeAttachment({ - contentType: IMAGE_PNG, - fileName: 'sax.png', - height: 1200, - url: pngUrl, - width: 800, - }), - ], - bottomOverlay: boolean('bottomOverlay', overrideProps.bottomOverlay || false), - direction: overrideProps.direction || 'incoming', - i18n, - isSticker: boolean('isSticker', overrideProps.isSticker || false), - onClick: action('onClick'), - onError: action('onError'), - stickerSize: number('stickerSize', overrideProps.stickerSize || 0), - tabIndex: number('tabIndex', overrideProps.tabIndex || 0), - withContentAbove: boolean( - 'withContentAbove', - overrideProps.withContentAbove || false - ), - withContentBelow: boolean( - 'withContentBelow', - overrideProps.withContentBelow || false - ), -}); - -export function OneImage(): JSX.Element { - const props = createProps(); - - return ; -} - -export function TwoImages(): JSX.Element { - const props = createProps({ + argTypes: { + bottomOverlay: { control: { type: 'boolean' } }, + isSticker: { control: { type: 'boolean' } }, + stickerSize: { control: { type: 'number' } }, + withContentAbove: { control: { type: 'boolean' } }, + withContentBelow: { control: { type: 'boolean' } }, + }, + args: { attachments: [ fakeAttachment({ contentType: IMAGE_PNG, @@ -71,267 +39,285 @@ export function TwoImages(): JSX.Element { url: pngUrl, width: 800, }), - fakeAttachment({ - contentType: IMAGE_JPEG, - fileName: 'tina-rolf-269345-unsplash.jpg', - height: 1680, - url: '/fixtures/tina-rolf-269345-unsplash.jpg', - width: 3000, - }), - ], - }); - - return ; -} - -export function ThreeImages(): JSX.Element { - const props = createProps({ - attachments: [ - fakeAttachment({ - contentType: IMAGE_PNG, - fileName: 'sax.png', - height: 1200, - url: pngUrl, - width: 800, - }), - fakeAttachment({ - contentType: IMAGE_JPEG, - fileName: 'tina-rolf-269345-unsplash.jpg', - height: 1680, - url: '/fixtures/tina-rolf-269345-unsplash.jpg', - width: 3000, - }), - fakeAttachment({ - contentType: IMAGE_PNG, - fileName: 'sax.png', - height: 1200, - url: pngUrl, - width: 800, - }), ], - }); + bottomOverlay: false, + direction: 'incoming', + i18n, + isSticker: false, + onClick: action('onClick'), + onError: action('onError'), + stickerSize: 0, + tabIndex: 0, + withContentAbove: false, + withContentBelow: false, + }, +} satisfies Meta; - return ; +export function OneImage(args: Props): JSX.Element { + return ; } -export function FourImages(): JSX.Element { - const props = createProps({ - attachments: [ - fakeAttachment({ - contentType: IMAGE_PNG, - fileName: 'sax.png', - height: 1200, - url: pngUrl, - width: 800, - }), - fakeAttachment({ - contentType: IMAGE_JPEG, - fileName: 'tina-rolf-269345-unsplash.jpg', - height: 1680, - url: '/fixtures/tina-rolf-269345-unsplash.jpg', - width: 3000, - }), - fakeAttachment({ - contentType: IMAGE_PNG, - fileName: 'sax.png', - height: 1200, - url: pngUrl, - width: 800, - }), - fakeAttachment({ - contentType: IMAGE_JPEG, - fileName: 'tina-rolf-269345-unsplash.jpg', - height: 1680, - url: '/fixtures/tina-rolf-269345-unsplash.jpg', - width: 3000, - }), - ], - }); - - return ; -} - -export function FiveImages(): JSX.Element { - const props = createProps({ - attachments: [ - fakeAttachment({ - contentType: IMAGE_PNG, - fileName: 'sax.png', - height: 1200, - url: pngUrl, - width: 800, - }), - fakeAttachment({ - contentType: IMAGE_JPEG, - fileName: 'tina-rolf-269345-unsplash.jpg', - height: 1680, - url: '/fixtures/tina-rolf-269345-unsplash.jpg', - width: 3000, - }), - fakeAttachment({ - contentType: IMAGE_PNG, - fileName: 'sax.png', - height: 1200, - url: pngUrl, - width: 800, - }), - fakeAttachment({ - contentType: IMAGE_JPEG, - fileName: 'tina-rolf-269345-unsplash.jpg', - height: 1680, - url: '/fixtures/tina-rolf-269345-unsplash.jpg', - width: 3000, - }), - fakeAttachment({ - contentType: IMAGE_PNG, - fileName: 'sax.png', - height: 1200, - url: pngUrl, - width: 800, - }), - ], - }); - - return ; -} - -export const _6Images = (): JSX.Element => { - const props = createProps({ - attachments: [ - fakeAttachment({ - contentType: IMAGE_PNG, - fileName: 'sax.png', - height: 1200, - url: pngUrl, - width: 800, - }), - fakeAttachment({ - contentType: IMAGE_JPEG, - fileName: 'tina-rolf-269345-unsplash.jpg', - height: 1680, - url: '/fixtures/tina-rolf-269345-unsplash.jpg', - width: 3000, - }), - fakeAttachment({ - contentType: IMAGE_PNG, - fileName: 'sax.png', - height: 1200, - url: pngUrl, - width: 800, - }), - fakeAttachment({ - contentType: IMAGE_JPEG, - fileName: 'tina-rolf-269345-unsplash.jpg', - height: 1680, - url: '/fixtures/tina-rolf-269345-unsplash.jpg', - width: 3000, - }), - fakeAttachment({ - contentType: IMAGE_PNG, - fileName: 'sax.png', - height: 1200, - url: pngUrl, - width: 800, - }), - fakeAttachment({ - contentType: IMAGE_PNG, - fileName: 'sax.png', - height: 1200, - url: pngUrl, - width: 800, - }), - fakeAttachment({ - contentType: IMAGE_PNG, - fileName: 'sax.png', - height: 1200, - url: pngUrl, - width: 800, - }), - ], - }); - - return ; -}; - -_6Images.story = { - name: '6+ Images', -}; - -export function MixedContentTypes(): JSX.Element { - const props = createProps({ - attachments: [ - fakeAttachment({ - contentType: VIDEO_MP4, - fileName: 'pixabay-Soap-Bubble-7141.mp4', - height: 112, - screenshot: { - height: 112, - width: 112, - url: '/fixtures/kitten-4-112-112.jpg', +export function TwoImages(args: Props): JSX.Element { + return ( + ; + fileName: 'tina-rolf-269345-unsplash.jpg', + height: 1680, + url: '/fixtures/tina-rolf-269345-unsplash.jpg', + width: 3000, + }), + ]} + /> + ); } -export function Sticker(): JSX.Element { - const props = createProps({ - attachments: [ - fakeAttachment({ - contentType: IMAGE_WEBP, - fileName: 'sticker.webp', - height: 512, - url: squareStickerUrl, - width: 512, - }), - ], - isSticker: true, - stickerSize: 128, - }); - - return ; +export function ThreeImages(args: Props): JSX.Element { + return ( + + ); } -export function ContentAboveAndBelow(): JSX.Element { - const props = createProps({ - withContentAbove: true, - withContentBelow: true, - }); - - return ; +export function FourImages(args: Props): JSX.Element { + return ( + + ); } -ContentAboveAndBelow.story = { - name: 'Content Above and Below', +export function FiveImages(args: Props): JSX.Element { + return ( + + ); +} + +export const _6Images = (args: Props): JSX.Element => { + return ( + + ); }; -export function BottomOverlay(): JSX.Element { - const props = createProps({ - bottomOverlay: true, - }); - - return ; +export function MixedContentTypes(args: Props): JSX.Element { + return ( + + ); +} + +export function Sticker(args: Props): JSX.Element { + return ( + + ); +} + +export function ContentAboveAndBelow(args: Props): JSX.Element { + return ; +} + +export function BottomOverlay(args: Props): JSX.Element { + return ; } diff --git a/ts/components/conversation/LastSeenIndicator.stories.tsx b/ts/components/conversation/LastSeenIndicator.stories.tsx index 138d8c9189..e659a29d3a 100644 --- a/ts/components/conversation/LastSeenIndicator.stories.tsx +++ b/ts/components/conversation/LastSeenIndicator.stories.tsx @@ -2,9 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - -import { number } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import type { Props } from './LastSeenIndicator'; import { LastSeenIndicator } from './LastSeenIndicator'; import { setupI18n } from '../../util/setupI18n'; @@ -14,26 +12,19 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/LastSeenIndicator', -}; + argTypes: { + count: { control: { type: 'number' } }, + }, + args: { + i18n, + count: 1, + }, +} satisfies Meta; -const createProps = (overrideProps: Partial = {}): Props => ({ - count: number('count', overrideProps.count || 1), - i18n, -}); - -export function One(): JSX.Element { - const props = createProps(); - return ; +export function One(args: Props): JSX.Element { + return ; } -export function MoreThanOne(): JSX.Element { - const props = createProps({ - count: 5, - }); - - return ; +export function MoreThanOne(args: Props): JSX.Element { + return ; } - -MoreThanOne.story = { - name: 'More than One', -}; diff --git a/ts/components/conversation/Linkify.stories.tsx b/ts/components/conversation/Linkify.stories.tsx index 23140ac386..46b7eae8a8 100644 --- a/ts/components/conversation/Linkify.stories.tsx +++ b/ts/components/conversation/Linkify.stories.tsx @@ -2,13 +2,13 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - +import type { Meta } from '@storybook/react'; import type { Props } from './Linkify'; import { Linkify } from './Linkify'; export default { title: 'Components/Conversation/Linkify', -}; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ renderNonLink: overrideProps.renderNonLink, @@ -31,10 +31,6 @@ export function LinksWithText(): JSX.Element { return ; } -LinksWithText.story = { - name: 'Links with Text', -}; - export function LinksWithEmojiWithoutSpace(): JSX.Element { const props = createProps({ text: '👍https://www.signal.org😎', @@ -43,10 +39,6 @@ export function LinksWithEmojiWithoutSpace(): JSX.Element { return ; } -LinksWithEmojiWithoutSpace.story = { - name: 'Links with Emoji without space', -}; - export function LinksWithEmojiAndText(): JSX.Element { const props = createProps({ text: 'https://example.com ⚠️ 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ https://example.com', @@ -55,10 +47,6 @@ export function LinksWithEmojiAndText(): JSX.Element { return ; } -LinksWithEmojiAndText.story = { - name: 'Links with Emoji and Text', -}; - export function NoLink(): JSX.Element { const props = createProps({ text: 'I am fond of cats', @@ -83,10 +71,6 @@ export function MissingProtocols(): JSX.Element { return ; } -MissingProtocols.story = { - name: 'Missing protocols', -}; - export function CustomTextRender(): JSX.Element { const props = createProps({ text: 'you should see this: https://www.signal.org - it is good. Also: https://placekitten.com!', diff --git a/ts/components/conversation/MandatoryProfileSharingActions.stories.tsx b/ts/components/conversation/MandatoryProfileSharingActions.stories.tsx index 8ae788209d..e0d355e13d 100644 --- a/ts/components/conversation/MandatoryProfileSharingActions.stories.tsx +++ b/ts/components/conversation/MandatoryProfileSharingActions.stories.tsx @@ -2,48 +2,52 @@ // 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 { Props as MandatoryProfileSharingActionsProps } from './MandatoryProfileSharingActions'; +import type { Meta } from '@storybook/react'; +import type { Props } from './MandatoryProfileSharingActions'; import { MandatoryProfileSharingActions } from './MandatoryProfileSharingActions'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; const i18n = setupI18n('en', enMessages); -const getBaseProps = ( - isGroup = false -): MandatoryProfileSharingActionsProps => ({ - conversationId: '123', - i18n, - conversationType: isGroup ? 'group' : 'direct', - firstName: text('firstName', 'Cayce'), - title: isGroup - ? text('title', 'NYC Rock Climbers') - : text('title', 'Cayce Bollard'), - acceptConversation: action('acceptConversation'), - blockAndReportSpam: action('blockAndReportSpam'), - blockConversation: action('blockConversation'), - deleteConversation: action('deleteConversation'), -}); - export default { title: 'Components/Conversation/MandatoryProfileSharingActions', -}; + argTypes: { + conversationType: { + control: { + type: 'select', + options: ['direct', 'group'], + }, + }, + firstName: { control: { type: 'text' } }, + title: { control: { type: 'text' } }, + }, + args: { + conversationId: '123', + i18n, + conversationType: 'direct', + firstName: 'Cayce', + title: 'Cayce Bollard', + acceptConversation: action('acceptConversation'), + blockAndReportSpam: action('blockAndReportSpam'), + blockConversation: action('blockConversation'), + deleteConversation: action('deleteConversation'), + }, +} satisfies Meta; -export function Direct(): JSX.Element { +export function Direct(args: Props): JSX.Element { return (
- +
); } -export function Group(): JSX.Element { +export function Group(args: Props): JSX.Element { return (
- +
); } diff --git a/ts/components/conversation/MessageBody.stories.tsx b/ts/components/conversation/MessageBody.stories.tsx index 83d76b1ce2..be793090d7 100644 --- a/ts/components/conversation/MessageBody.stories.tsx +++ b/ts/components/conversation/MessageBody.stories.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import type { Props } from './MessageBody'; import { MessageBody } from './MessageBody'; import { setupI18n } from '../../util/setupI18n'; @@ -28,7 +28,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/MessageBody', -}; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ bodyRanges: overrideProps.bodyRanges, @@ -107,10 +107,6 @@ export function JumbomojiDisabledByText(): JSX.Element { return ; } -JumbomojiDisabledByText.story = { - name: 'Jumbomoji Disabled by Text', -}; - export function TextPending(): JSX.Element { const props = createProps({ text: 'Check out https://www.signal.org', @@ -139,10 +135,6 @@ export function Mention(): JSX.Element { return ; } -Mention.story = { - name: '@Mention', -}; - export function MultipleMentions(): JSX.Element { const props = createProps({ // These are intentionally in a mixed order to test how we deal with that @@ -181,10 +173,6 @@ export function MultipleMentions(): JSX.Element { ); } -MultipleMentions.story = { - name: 'Multiple @Mentions', -}; - export function ComplexMessageBody(): JSX.Element { const props = createProps({ bodyRanges: [ @@ -224,10 +212,6 @@ export function ComplexMessageBody(): JSX.Element { ); } -ComplexMessageBody.story = { - name: 'Complex MessageBody', -}; - export function FormattingBasic(): JSX.Element { const [isSpoilerExpanded, setIsSpoilerExpanded] = React.useState({}); diff --git a/ts/components/conversation/MessageBodyReadMore.stories.tsx b/ts/components/conversation/MessageBodyReadMore.stories.tsx index 24add4f29b..27e3f8f32e 100644 --- a/ts/components/conversation/MessageBodyReadMore.stories.tsx +++ b/ts/components/conversation/MessageBodyReadMore.stories.tsx @@ -2,9 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import React, { useState } from 'react'; - import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import type { Props } from './MessageBodyReadMore'; import { MessageBodyReadMore } from './MessageBodyReadMore'; import { setupI18n } from '../../util/setupI18n'; @@ -18,7 +17,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/MessageBodyReadMore', -}; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ bodyRanges: overrideProps.bodyRanges, @@ -68,20 +67,12 @@ export function LongText100More(): JSX.Element { ); } -LongText100More.story = { - name: 'Long text + 100 more', -}; - export function LotsOfCakeWithSomeCherriesOnTop(): JSX.Element { return ( ); } -LotsOfCakeWithSomeCherriesOnTop.story = { - name: 'Lots of cake with some cherries on top', -}; - export function LeafyNotBuffered(): JSX.Element { return ; } @@ -154,10 +145,6 @@ export function LongTextMostlySpoiler(): JSX.Element { ); } -LeafyNotBuffered.story = { - name: 'Leafy not buffered', -}; - export function Links(): JSX.Element { return ( ; } -ExcessiveAmountsOfCake.story = { - name: 'Excessive amounts of cake', -}; - export function LongText(): JSX.Element { return ( ); } - -LongText.story = { - name: 'Long text', -}; diff --git a/ts/components/conversation/MessageDetail.stories.tsx b/ts/components/conversation/MessageDetail.stories.tsx index 13d9025914..6d9f01bff5 100644 --- a/ts/components/conversation/MessageDetail.stories.tsx +++ b/ts/components/conversation/MessageDetail.stories.tsx @@ -2,10 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - import { action } from '@storybook/addon-actions'; -import { number } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import type { PropsData as MessageDataPropsType } from './Message'; import { TextDirection } from './Message'; import type { Props } from './MessageDetail'; @@ -20,10 +18,6 @@ import { ThemeType } from '../../types/Util'; const i18n = setupI18n('en', enMessages); -export default { - title: 'Components/Conversation/MessageDetail', -}; - const defaultMessage: MessageDataPropsType = { author: getDefaultConversation({ id: 'some-id', @@ -51,206 +45,224 @@ const defaultMessage: MessageDataPropsType = { timestamp: Date.now(), }; -const createProps = (overrideProps: Partial = {}): Props => ({ - contacts: overrideProps.contacts || [ - { - ...getDefaultConversation({ - title: 'Just Max', - }), - isOutgoingKeyError: false, - isUnidentifiedDelivery: false, - status: SendStatus.Delivered, - }, - ], - errors: overrideProps.errors || [], - message: overrideProps.message || defaultMessage, - receivedAt: number('receivedAt', overrideProps.receivedAt || Date.now()), - sentAt: number('sentAt', overrideProps.sentAt || Date.now()), - - getPreferredBadge: () => getFakeBadge(), - i18n, - platform: 'darwin', - interactionMode: 'keyboard', - theme: ThemeType.light, - - toggleSafetyNumberModal: action('toggleSafetyNumberModal'), - - checkForAccount: action('checkForAccount'), - clearTargetedMessage: action('clearTargetedMessage'), - showLightboxForViewOnceMedia: action('showLightboxForViewOnceMedia'), - doubleCheckMissingQuoteReference: action('doubleCheckMissingQuoteReference'), - kickOffAttachmentDownload: action('kickOffAttachmentDownload'), - markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'), - messageExpanded: action('messageExpanded'), - showConversation: action('showConversation'), - openGiftBadge: action('openGiftBadge'), - renderAudioAttachment: () =>
*AudioAttachment*
, - saveAttachment: action('saveAttachment'), - showSpoiler: action('showSpoiler'), - retryMessageSend: action('retryMessageSend'), - pushPanelForConversation: action('pushPanelForConversation'), - showContactModal: action('showContactModal'), - showExpiredIncomingTapToViewToast: action( - 'showExpiredIncomingTapToViewToast' - ), - showExpiredOutgoingTapToViewToast: action( - 'showExpiredOutgoingTapToViewToast' - ), - showLightbox: action('showLightbox'), - startConversation: action('startConversation'), - viewStory: action('viewStory'), -}); - -export function DeliveredIncoming(): JSX.Element { - const props = createProps({ +export default { + title: 'Components/Conversation/MessageDetail', + argTypes: { + message: { control: { type: 'text' } }, + receivedAt: { control: { type: 'number' } }, + sentAt: { control: { type: 'number' } }, + }, + args: { contacts: [ { ...getDefaultConversation({ - color: 'forest', - title: 'Max', - }), - status: undefined, - isOutgoingKeyError: false, - isUnidentifiedDelivery: false, - }, - ], - }); - return ; -} - -export function DeliveredOutgoing(): JSX.Element { - const props = createProps({ - message: { - ...defaultMessage, - direction: 'outgoing', - text: 'A message to Max', - }, - }); - return ; -} - -export function MessageStatuses(): JSX.Element { - const props = createProps({ - contacts: [ - { - ...getDefaultConversation({ - title: 'Max', - }), - isOutgoingKeyError: false, - isUnidentifiedDelivery: false, - status: SendStatus.Sent, - }, - { - ...getDefaultConversation({ - title: 'Sally', - }), - isOutgoingKeyError: false, - isUnidentifiedDelivery: false, - status: SendStatus.Pending, - }, - { - ...getDefaultConversation({ - title: 'Terry', - }), - isOutgoingKeyError: false, - isUnidentifiedDelivery: false, - status: SendStatus.Failed, - }, - { - ...getDefaultConversation({ - title: 'Theo', + title: 'Just Max', }), isOutgoingKeyError: false, isUnidentifiedDelivery: false, status: SendStatus.Delivered, }, - { - ...getDefaultConversation({ - title: 'Nikki', - }), - isOutgoingKeyError: false, - isUnidentifiedDelivery: false, - status: SendStatus.Read, - }, ], - message: { - ...defaultMessage, - conversationType: 'group', - text: 'A message to you all!', - }, - }); - return ; + errors: [], + message: defaultMessage, + receivedAt: Date.now(), + sentAt: Date.now(), + + getPreferredBadge: () => getFakeBadge(), + i18n, + platform: 'darwin', + interactionMode: 'keyboard', + theme: ThemeType.light, + + toggleSafetyNumberModal: action('toggleSafetyNumberModal'), + + checkForAccount: action('checkForAccount'), + clearTargetedMessage: action('clearTargetedMessage'), + showLightboxForViewOnceMedia: action('showLightboxForViewOnceMedia'), + doubleCheckMissingQuoteReference: action( + 'doubleCheckMissingQuoteReference' + ), + kickOffAttachmentDownload: action('kickOffAttachmentDownload'), + markAttachmentAsCorrupted: action('markAttachmentAsCorrupted'), + messageExpanded: action('messageExpanded'), + showConversation: action('showConversation'), + openGiftBadge: action('openGiftBadge'), + renderAudioAttachment: () =>
AudioAttachment
, + saveAttachment: action('saveAttachment'), + showSpoiler: action('showSpoiler'), + retryMessageSend: action('retryMessageSend'), + pushPanelForConversation: action('pushPanelForConversation'), + showContactModal: action('showContactModal'), + showExpiredIncomingTapToViewToast: action( + 'showExpiredIncomingTapToViewToast' + ), + showExpiredOutgoingTapToViewToast: action( + 'showExpiredOutgoingTapToViewToast' + ), + showLightbox: action('showLightbox'), + startConversation: action('startConversation'), + viewStory: action('viewStory'), + }, +} satisfies Meta; + +export function DeliveredIncoming(args: Props): JSX.Element { + return ( + + ); } -export function NotDelivered(): JSX.Element { - const props = createProps({ - message: { - ...defaultMessage, - direction: 'outgoing', - text: 'A message to Max', - }, - }); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - props.receivedAt = undefined as any; - - return ; +export function DeliveredOutgoing(args: Props): JSX.Element { + return ( + + ); } -export function NoContacts(): JSX.Element { - const props = createProps({ - contacts: [], - message: { - ...defaultMessage, - direction: 'outgoing', - text: 'Is anybody there?', - }, - }); - return ; +export function MessageStatuses(args: Props): JSX.Element { + return ( + + ); } -export function AllErrors(): JSX.Element { - const props = createProps({ - errors: [ - { - name: 'Another Error', - message: 'Wow, that went bad.', - }, - ], - message: { - ...defaultMessage, - }, - contacts: [ - { - ...getDefaultConversation({ - title: 'Max', - }), - isOutgoingKeyError: true, - isUnidentifiedDelivery: false, - status: SendStatus.Failed, - }, - { - ...getDefaultConversation({ - title: 'Sally', - }), - errors: [ - { - name: 'Big Error', - message: 'Stuff happened, in a bad way.', - }, - ], - isOutgoingKeyError: false, - isUnidentifiedDelivery: true, - status: SendStatus.Failed, - }, - { - ...getDefaultConversation({ - title: 'Terry', - }), - isOutgoingKeyError: true, - isUnidentifiedDelivery: true, - status: SendStatus.Failed, - }, - ], - }); - return ; +export function NotDelivered(args: Props): JSX.Element { + return ( + + ); +} + +export function NoContacts(args: Props): JSX.Element { + return ( + + ); +} + +export function AllErrors(args: Props): JSX.Element { + return ( + + ); } diff --git a/ts/components/conversation/MessageRequestActions.stories.tsx b/ts/components/conversation/MessageRequestActions.stories.tsx index 81586efbff..a4411f9084 100644 --- a/ts/components/conversation/MessageRequestActions.stories.tsx +++ b/ts/components/conversation/MessageRequestActions.stories.tsx @@ -2,70 +2,61 @@ // 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 { Props as MessageRequestActionsProps } from './MessageRequestActions'; +import type { Meta } from '@storybook/react'; +import type { Props } from './MessageRequestActions'; import { MessageRequestActions } from './MessageRequestActions'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; const i18n = setupI18n('en', enMessages); -const getBaseProps = (isGroup = false): MessageRequestActionsProps => ({ - conversationId: '123', - i18n, - conversationType: isGroup ? 'group' : 'direct', - firstName: text('firstName', 'Cayce'), - title: isGroup - ? text('title', 'NYC Rock Climbers') - : text('title', 'Cayce Bollard'), - acceptConversation: action('acceptConversation'), - blockAndReportSpam: action('blockAndReportSpam'), - blockConversation: action('blockConversation'), - deleteConversation: action('deleteConversation'), -}); - export default { title: 'Components/Conversation/MessageRequestActions', -}; + argTypes: { + conversationType: { + control: { + type: 'select', + options: ['direct', 'group'], + }, + }, + firstName: { control: { type: 'text' } }, + title: { control: { type: 'text' } }, + }, + args: { + conversationId: '123', + i18n, + conversationType: 'direct', + firstName: 'Cayce', + title: 'Cayce Bollard', + acceptConversation: action('acceptConversation'), + blockAndReportSpam: action('blockAndReportSpam'), + blockConversation: action('blockConversation'), + deleteConversation: action('deleteConversation'), + }, + decorators: [ + (Story: React.ComponentType): JSX.Element => { + return ( +
+ +
+ ); + }, + ], +} satisfies Meta; -export function Direct(): JSX.Element { - return ( -
- -
- ); +export function Direct(args: Props): JSX.Element { + return ; } -export function DirectBlocked(): JSX.Element { - return ( -
- -
- ); +export function DirectBlocked(args: Props): JSX.Element { + return ; } -DirectBlocked.story = { - name: 'Direct (Blocked)', -}; - -export function Group(): JSX.Element { - return ( -
- -
- ); +export function Group(args: Props): JSX.Element { + return ; } -export function GroupBlocked(): JSX.Element { - return ( -
- -
- ); +export function GroupBlocked(args: Props): JSX.Element { + return ; } - -GroupBlocked.story = { - name: 'Group (Blocked)', -}; diff --git a/ts/components/conversation/MessageTimestamp.stories.tsx b/ts/components/conversation/MessageTimestamp.stories.tsx index a161a7a7fc..eb2f0a7352 100644 --- a/ts/components/conversation/MessageTimestamp.stories.tsx +++ b/ts/components/conversation/MessageTimestamp.stories.tsx @@ -2,8 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; -import { boolean, date, select, text } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; import type { Props } from './MessageTimestamp'; @@ -11,10 +10,6 @@ import { MessageTimestamp } from './MessageTimestamp'; const i18n = setupI18n('en', enMessages); -export default { - title: 'Components/Conversation/MessageTimestamp', -}; - const { now } = Date; const seconds = (n: number) => n * 1000; const minutes = (n: number) => 60 * seconds(n); @@ -40,51 +35,57 @@ const times = (): Array<[string, number]> => [ ['366d ago', now() - days(366)], ]; -const createProps = (overrideProps: Partial = {}): Props => ({ - i18n, - timestamp: overrideProps.timestamp || Date.now(), - module: text('module', ''), - withImageNoCaption: boolean('withImageNoCaption', false), - withSticker: boolean('withSticker', false), - withTapToViewExpired: boolean('withTapToViewExpired', false), - direction: - select( - 'direction', - { none: '', incoming: 'incoming', outgoing: 'outgoing' }, - '' - ) || undefined, -}); +export default { + title: 'Components/Conversation/MessageTimestamp', + argTypes: { + timestamp: { control: { type: 'number' } }, + module: { control: { type: 'text' } }, + withImageNoCaption: { control: { type: 'boolean' } }, + withSticker: { control: { type: 'boolean' } }, + withTapToViewExpired: { control: { type: 'boolean' } }, + direction: { + control: { + type: 'select', + options: ['', 'incoming', 'outgoing'], + }, + }, + }, + args: { + i18n, + timestamp: Date.now(), + module: '', + withImageNoCaption: false, + withSticker: false, + withTapToViewExpired: false, + direction: undefined, + }, +} satisfies Meta; -const createTable = (overrideProps: Partial = {}) => ( - - - - - - - {times().map(([description, timestamp]) => ( - - - +export function Normal(args: Props): JSX.Element { + return ( +
DescriptionTimestamp
{description} - -
+ + + + - ))} - -
DescriptionTimestamp
-); - -export const Normal = (): JSX.Element => { - return createTable(); -}; - -export function Knobs(): JSX.Element { - const props = createProps({ - timestamp: date('timestamp', new Date()), - }); - - return ; + {times().map(([description, timestamp]) => ( + + {description} + + + + + ))} + + + ); +} + +export function Knobs(args: Props): JSX.Element { + return ; } diff --git a/ts/components/conversation/ProfileChangeNotification.stories.tsx b/ts/components/conversation/ProfileChangeNotification.stories.tsx index 49bb4b4e01..ab32816be1 100644 --- a/ts/components/conversation/ProfileChangeNotification.stories.tsx +++ b/ts/components/conversation/ProfileChangeNotification.stories.tsx @@ -2,17 +2,18 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - +import type { Meta } from '@storybook/react'; import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; +import type { PropsType } from './ProfileChangeNotification'; import { ProfileChangeNotification } from './ProfileChangeNotification'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ProfileChangeNotification', -}; +} satisfies Meta; export function FromContact(): JSX.Element { return ( @@ -33,10 +34,6 @@ export function FromContact(): JSX.Element { ); } -FromContact.story = { - name: 'From contact', -}; - export function FromNonContact(): JSX.Element { return ( ); } - -FromContactWithLongNamesBeforeAndAfter.story = { - name: 'From contact with long names before and after', -}; diff --git a/ts/components/conversation/Quote.stories.tsx b/ts/components/conversation/Quote.stories.tsx index 52ed0d697d..b359b19c6d 100644 --- a/ts/components/conversation/Quote.stories.tsx +++ b/ts/components/conversation/Quote.stories.tsx @@ -1,7 +1,7 @@ // 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'; @@ -34,49 +34,40 @@ export default { component: Quote, title: 'Components/Conversation/Quote', argTypes: { - authorTitle: { - defaultValue: 'Default Sender', - }, - conversationColor: { - defaultValue: 'forest', - }, - doubleCheckMissingQuoteReference: { action: true }, - i18n: { - defaultValue: i18n, - }, - platform: { - defautlValue: 'darwin', - }, isFromMe: { control: { type: 'checkbox' }, - defaultValue: false, }, isGiftBadge: { control: { type: 'checkbox' }, - defaultValue: false, }, isIncoming: { control: { type: 'checkbox' }, - defaultValue: false, }, isViewOnce: { control: { type: 'checkbox' }, - defaultValue: false, - }, - onClick: { action: true }, - onClose: { action: true }, - rawAttachment: { - defaultValue: undefined, }, referencedMessageNotFound: { control: { type: 'checkbox' }, - defaultValue: false, - }, - text: { - defaultValue: 'A sample message from a pal', }, }, -} as Meta; + args: { + authorTitle: 'Default Sender', + conversationColor: 'forest', + doubleCheckMissingQuoteReference: action( + 'doubleCheckMissingQuoteReference' + ), + i18n, + isFromMe: false, + isGiftBadge: false, + isIncoming: false, + isViewOnce: false, + onClick: action('onClick'), + onClose: action('onClose'), + rawAttachment: undefined, + referencedMessageNotFound: false, + text: 'A sample message from a pal', + }, +} satisfies Meta; const defaultMessageProps: TimelineMessagesProps = { author: getDefaultConversation({ @@ -197,42 +188,30 @@ const renderInMessage = ({ }; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; -const TemplateInMessage: Story = args => renderInMessage(args); +const Template: StoryFn = args => ; +const TemplateInMessage: StoryFn = args => renderInMessage(args); export const OutgoingByAnotherAuthor = Template.bind({}); OutgoingByAnotherAuthor.args = { authorTitle: getDefaultConversation().title, }; -OutgoingByAnotherAuthor.story = { - name: 'Outgoing by Another Author', -}; export const OutgoingByMe = Template.bind({}); OutgoingByMe.args = { isFromMe: true, }; -OutgoingByMe.story = { - name: 'Outgoing by Me', -}; export const IncomingByAnotherAuthor = Template.bind({}); IncomingByAnotherAuthor.args = { authorTitle: getDefaultConversation().title, isIncoming: true, }; -IncomingByAnotherAuthor.story = { - name: 'Incoming by Another Author', -}; export const IncomingByMe = Template.bind({}); IncomingByMe.args = { isFromMe: true, isIncoming: true, }; -IncomingByMe.story = { - name: 'Incoming by Me', -}; export function IncomingOutgoingColors(args: Props): JSX.Element { return ( @@ -244,9 +223,6 @@ export function IncomingOutgoingColors(args: Props): JSX.Element { ); } IncomingOutgoingColors.args = {}; -IncomingOutgoingColors.story = { - name: 'Incoming/Outgoing Colors', -}; export const ImageOnly = Template.bind({}); ImageOnly.args = { @@ -289,9 +265,6 @@ ImageAttachmentNoThumbnail.args = { isVoiceMessage: false, }, }; -ImageAttachmentNoThumbnail.story = { - name: 'Image Attachment w/o Thumbnail', -}; export const ImageTapToView = Template.bind({}); ImageTapToView.args = { @@ -303,9 +276,6 @@ ImageTapToView.args = { isVoiceMessage: false, }, }; -ImageTapToView.story = { - name: 'Image Tap-to-View', -}; export const VideoOnly = Template.bind({}); VideoOnly.args = { @@ -348,9 +318,6 @@ VideoAttachmentNoThumbnail.args = { isVoiceMessage: false, }, }; -VideoAttachmentNoThumbnail.story = { - name: 'Video Attachment w/o Thumbnail', -}; export const VideoTapToView = Template.bind({}); VideoTapToView.args = { @@ -362,9 +329,6 @@ VideoTapToView.args = { isVoiceMessage: false, }, }; -VideoTapToView.story = { - name: 'Video Tap-to-View', -}; export const GiftBadge = TemplateInMessage.bind({}); GiftBadge.args = { @@ -430,9 +394,6 @@ MediaTapToView.args = { isVoiceMessage: false, }, }; -MediaTapToView.story = { - name: 'Media Tap-to-View', -}; export const OtherFileAttachment = Template.bind({}); OtherFileAttachment.args = { @@ -451,9 +412,6 @@ LongMessageAttachmentShouldBeHidden.args = { isVoiceMessage: false, }, }; -LongMessageAttachmentShouldBeHidden.story = { - name: 'Long message attachment (should be hidden)', -}; export const NoCloseButton = Template.bind({}); NoCloseButton.args = { @@ -469,27 +427,18 @@ export const MissingTextAttachment = Template.bind({}); MissingTextAttachment.args = { text: undefined, }; -MissingTextAttachment.story = { - name: 'Missing Text & Attachment', -}; export const MentionOutgoingAnotherAuthor = Template.bind({}); MentionOutgoingAnotherAuthor.args = { authorTitle: 'Tony Stark', text: '@Captain America Lunch later?', }; -MentionOutgoingAnotherAuthor.story = { - name: '@mention + outgoing + another author', -}; export const MentionOutgoingMe = Template.bind({}); MentionOutgoingMe.args = { isFromMe: true, text: '@Captain America Lunch later?', }; -MentionOutgoingMe.story = { - name: '@mention + outgoing + me', -}; export const MentionIncomingAnotherAuthor = Template.bind({}); MentionIncomingAnotherAuthor.args = { @@ -497,9 +446,6 @@ MentionIncomingAnotherAuthor.args = { isIncoming: true, text: '@Tony Stark sure', }; -MentionIncomingAnotherAuthor.story = { - name: '@mention + incoming + another author', -}; export const MentionIncomingMe = Template.bind({}); MentionIncomingMe.args = { @@ -507,9 +453,6 @@ MentionIncomingMe.args = { isIncoming: true, text: '@Tony Stark sure', }; -MentionIncomingMe.story = { - name: '@mention + incoming + me', -}; export function CustomColor(args: Props): JSX.Element { return ( @@ -551,9 +494,6 @@ IsStoryReply.args = { isVoiceMessage: false, }, }; -IsStoryReply.story = { - name: 'isStoryReply', -}; export const IsStoryReplyEmoji = Template.bind({}); IsStoryReplyEmoji.args = { @@ -575,9 +515,6 @@ IsStoryReplyEmoji.args = { }, reactionEmoji: '🏋️', }; -IsStoryReplyEmoji.story = { - name: 'isStoryReply emoji', -}; export const Payment = Template.bind({}); Payment.args = { diff --git a/ts/components/conversation/ReactionPicker.stories.tsx b/ts/components/conversation/ReactionPicker.stories.tsx index bb62616fcd..fbd6992513 100644 --- a/ts/components/conversation/ReactionPicker.stories.tsx +++ b/ts/components/conversation/ReactionPicker.stories.tsx @@ -2,9 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as 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 { Props as ReactionPickerProps } from './ReactionPicker'; @@ -33,7 +32,7 @@ const renderEmojiPicker: ReactionPickerProps['renderEmojiPicker'] = ({ export default { title: 'Components/Conversation/ReactionPicker', -}; +} satisfies Meta; export function Base(): JSX.Element { return ( diff --git a/ts/components/conversation/ReactionViewer.stories.tsx b/ts/components/conversation/ReactionViewer.stories.tsx index 4193a3a9a9..2a78ddfc13 100644 --- a/ts/components/conversation/ReactionViewer.stories.tsx +++ b/ts/components/conversation/ReactionViewer.stories.tsx @@ -2,9 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import type { Props } from './ReactionViewer'; import { ReactionViewer } from './ReactionViewer'; import { setupI18n } from '../../util/setupI18n'; @@ -16,7 +15,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ReactionViewer', -}; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ getPreferredBadge: () => undefined, diff --git a/ts/components/conversation/ResetSessionNotification.stories.tsx b/ts/components/conversation/ResetSessionNotification.stories.tsx index ed81ac1889..7b02146b50 100644 --- a/ts/components/conversation/ResetSessionNotification.stories.tsx +++ b/ts/components/conversation/ResetSessionNotification.stories.tsx @@ -2,7 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - +import type { Meta } from '@storybook/react'; +import type { Props } from './ResetSessionNotification'; import { ResetSessionNotification } from './ResetSessionNotification'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; @@ -11,7 +12,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ResetSessionNotification', -}; +} satisfies Meta; export function Notification(): JSX.Element { return ; diff --git a/ts/components/conversation/SafetyNumberNotification.stories.tsx b/ts/components/conversation/SafetyNumberNotification.stories.tsx index 8cc15a15f9..20c91cf88d 100644 --- a/ts/components/conversation/SafetyNumberNotification.stories.tsx +++ b/ts/components/conversation/SafetyNumberNotification.stories.tsx @@ -3,8 +3,7 @@ import * as React from 'react'; import { action } from '@storybook/addon-actions'; -import { boolean, text } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; import type { ContactType, Props } from './SafetyNumberNotification'; @@ -14,53 +13,54 @@ const i18n = setupI18n('en', enMessages); const createContact = (props: Partial): ContactType => ({ id: '', - title: text('contact title', props.title || ''), -}); - -const createProps = (overrideProps: Partial = {}): Props => ({ - i18n, - contact: overrideProps.contact || ({} as ContactType), - isGroup: boolean('isGroup', overrideProps.isGroup || false), - toggleSafetyNumberModal: action('toggleSafetyNumberModal'), + title: props.title ?? '', }); export default { title: 'Components/Conversation/SafetyNumberNotification', -}; - -export function GroupConversation(): JSX.Element { - const props = createProps({ - isGroup: true, - contact: createContact({ - title: 'Mr. Fire', - }), - }); - - return ; -} - -export function DirectConversation(): JSX.Element { - const props = createProps({ + argTypes: { + isGroup: { control: { type: 'boolean' } }, + }, + args: { + i18n, + contact: {} as ContactType, isGroup: false, - contact: createContact({ - title: 'Mr. Fire', - }), - }); + toggleSafetyNumberModal: action('toggleSafetyNumberModal'), + }, +} satisfies Meta; - return ; +export function GroupConversation(args: Props): JSX.Element { + return ( + + ); } -export function LongNameInGroup(): JSX.Element { - const props = createProps({ - isGroup: true, - contact: createContact({ - title: '🐈‍⬛🍕🎂'.repeat(50), - }), - }); - - return ; +export function DirectConversation(args: Props): JSX.Element { + return ( + + ); } -LongNameInGroup.story = { - name: 'Long name in group', -}; +export function LongNameInGroup(args: Props): JSX.Element { + return ( + + ); +} diff --git a/ts/components/conversation/ScrollDownButton.stories.tsx b/ts/components/conversation/ScrollDownButton.stories.tsx index b9bafb6e95..d7574a7b10 100644 --- a/ts/components/conversation/ScrollDownButton.stories.tsx +++ b/ts/components/conversation/ScrollDownButton.stories.tsx @@ -2,7 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; -import type { Meta, Story } from '@storybook/react'; +import type { Meta, StoryFn } from '@storybook/react'; import { action } from '@storybook/addon-actions'; import { setupI18n } from '../../util/setupI18n'; @@ -35,10 +35,10 @@ export default { }, }, }, -} as Meta; +} satisfies Meta; // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ( +const Template: StoryFn = args => ( ); @@ -46,14 +46,8 @@ export const UnreadMessages = Template.bind({}); UnreadMessages.args = createProps({ variant: ScrollDownButtonVariant.UNREAD_MESSAGES, }); -UnreadMessages.story = { - name: 'UnreadMessages', -}; export const UnreadMentions = Template.bind({}); UnreadMentions.args = createProps({ variant: ScrollDownButtonVariant.UNREAD_MENTIONS, }); -UnreadMentions.story = { - name: 'UnreadMentions', -}; diff --git a/ts/components/conversation/StagedGenericAttachment.stories.tsx b/ts/components/conversation/StagedGenericAttachment.stories.tsx index 01e9ec97b1..5b17fa3171 100644 --- a/ts/components/conversation/StagedGenericAttachment.stories.tsx +++ b/ts/components/conversation/StagedGenericAttachment.stories.tsx @@ -2,9 +2,8 @@ // 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 { AttachmentType } from '../../types/Attachment'; import { stringToMIMEType } from '../../types/MIME'; import { setupI18n } from '../../util/setupI18n'; @@ -16,51 +15,51 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/StagedGenericAttachment', -}; - -const createProps = (overrideProps: Partial = {}): Props => ({ - attachment: overrideProps.attachment || ({} as AttachmentType), - i18n, - onClose: action('onClose'), -}); + argTypes: {}, + args: { + attachment: { + contentType: stringToMIMEType(''), + fileName: '', + url: '', + size: 14243, + }, + i18n, + onClose: action('onClose'), + }, +} satisfies Meta; const createAttachment = ( props: Partial = {} ): AttachmentType => ({ - contentType: stringToMIMEType( - text('attachment contentType', props.contentType || '') - ), - fileName: text('attachment fileName', props.fileName || ''), + contentType: stringToMIMEType(props.contentType ?? ''), + fileName: props.fileName ?? '', url: '', size: 14243, }); -export function TextFile(): JSX.Element { +export function TextFile(args: Props): JSX.Element { const attachment = createAttachment({ contentType: stringToMIMEType('text/plain'), fileName: 'manifesto.txt', }); - const props = createProps({ attachment }); - return ; + return ; } -export function LongName(): JSX.Element { +export function LongName(args: Props): JSX.Element { const attachment = createAttachment({ contentType: stringToMIMEType('text/plain'), fileName: 'this-is-my-very-important-manifesto-you-must-read-it.txt', }); - const props = createProps({ attachment }); - return ; + return ; } -export function LongExtension(): JSX.Element { +export function LongExtension(args: Props): JSX.Element { const attachment = createAttachment({ contentType: stringToMIMEType('text/plain'), fileName: 'manifesto.reallylongtxt', }); - const props = createProps({ attachment }); - return ; + return ; } diff --git a/ts/components/conversation/StagedLinkPreview.stories.tsx b/ts/components/conversation/StagedLinkPreview.stories.tsx index 7b7966fd8d..093bcbccd6 100644 --- a/ts/components/conversation/StagedLinkPreview.stories.tsx +++ b/ts/components/conversation/StagedLinkPreview.stories.tsx @@ -1,7 +1,7 @@ // 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'; @@ -22,7 +22,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/StagedLinkPreview', component: StagedLinkPreview, -} as Meta; +} satisfies Meta; const getDefaultProps = (): Props => ({ date: Date.now(), @@ -35,7 +35,7 @@ const getDefaultProps = (): Props => ({ }); // eslint-disable-next-line react/function-component-definition -const Template: Story = args => ; +const Template: StoryFn = args => ; export const Loading = Template.bind({}); Loading.args = { @@ -44,6 +44,9 @@ Loading.args = { }; export const NoImage = Template.bind({}); +NoImage.args = { + ...getDefaultProps(), +}; export const Image = Template.bind({}); Image.args = { @@ -65,18 +68,12 @@ ImageNoTitleOrDescription.args = { contentType: IMAGE_JPEG, }), }; -ImageNoTitleOrDescription.story = { - name: 'Image, No Title Or Description', -}; export const NoImageLongTitleWithDescription = Template.bind({}); NoImageLongTitleWithDescription.args = { ...getDefaultProps(), title: LONG_TITLE, }; -NoImageLongTitleWithDescription.story = { - name: 'No Image, Long Title With Description', -}; export const NoImageLongTitleWithoutDescription = Template.bind({}); NoImageLongTitleWithoutDescription.args = { @@ -84,9 +81,6 @@ NoImageLongTitleWithoutDescription.args = { title: LONG_TITLE, description: '', }; -NoImageLongTitleWithoutDescription.story = { - name: 'No Image, Long Title Without Description', -}; export const ImageLongTitleWithoutDescription = Template.bind({}); ImageLongTitleWithoutDescription.args = { @@ -97,9 +91,6 @@ ImageLongTitleWithoutDescription.args = { contentType: IMAGE_JPEG, }), }; -ImageLongTitleWithoutDescription.story = { - name: 'Image, Long Title Without Description', -}; export const ImageLongTitleAndDescription = Template.bind({}); ImageLongTitleAndDescription.args = { @@ -111,9 +102,6 @@ ImageLongTitleAndDescription.args = { contentType: IMAGE_JPEG, }), }; -ImageLongTitleAndDescription.story = { - name: 'Image, Long Title And Description', -}; export const EverythingImageTitleDescriptionAndDate = Template.bind({}); EverythingImageTitleDescriptionAndDate.args = { @@ -125,6 +113,3 @@ EverythingImageTitleDescriptionAndDate.args = { contentType: IMAGE_JPEG, }), }; -EverythingImageTitleDescriptionAndDate.story = { - name: 'Everything: image, title, description, and date', -}; diff --git a/ts/components/conversation/StagedPlaceholderAttachment.stories.tsx b/ts/components/conversation/StagedPlaceholderAttachment.stories.tsx index d0cad52149..57456b6b7e 100644 --- a/ts/components/conversation/StagedPlaceholderAttachment.stories.tsx +++ b/ts/components/conversation/StagedPlaceholderAttachment.stories.tsx @@ -3,16 +3,17 @@ import * as 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 { Props } from './StagedPlaceholderAttachment'; import { StagedPlaceholderAttachment } from './StagedPlaceholderAttachment'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/StagedPlaceholderAttachment', -}; +} satisfies Meta; export function Default(): JSX.Element { return ( diff --git a/ts/components/conversation/StagedPlaceholderAttachment.tsx b/ts/components/conversation/StagedPlaceholderAttachment.tsx index ede2c9a35f..39293e4292 100644 --- a/ts/components/conversation/StagedPlaceholderAttachment.tsx +++ b/ts/components/conversation/StagedPlaceholderAttachment.tsx @@ -4,7 +4,7 @@ import React from 'react'; import type { LocalizerType } from '../../types/Util'; -type Props = { +export type Props = { onClick: () => void; i18n: LocalizerType; }; diff --git a/ts/components/conversation/SystemMessage.stories.tsx b/ts/components/conversation/SystemMessage.stories.tsx index 30f87abee0..e04e2bc164 100644 --- a/ts/components/conversation/SystemMessage.stories.tsx +++ b/ts/components/conversation/SystemMessage.stories.tsx @@ -2,11 +2,13 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; +import type { Meta } from '@storybook/react'; +import type { PropsType } from './SystemMessage'; import { SystemMessage, SystemMessageKind } from './SystemMessage'; export default { title: 'Components/Conversation/SystemMessage', -}; +} satisfies Meta; export function PlainSystemMessage(): JSX.Element { return ( @@ -18,10 +20,6 @@ export function PlainSystemMessage(): JSX.Element { ); } -PlainSystemMessage.story = { - name: 'Plain', -}; - export function DangerSystemMessage(): JSX.Element { return ( ); } - -ErrorSystemMessage.story = { - name: 'Error', -}; diff --git a/ts/components/conversation/SystemMessage.tsx b/ts/components/conversation/SystemMessage.tsx index df90b99c19..2c977166e7 100644 --- a/ts/components/conversation/SystemMessage.tsx +++ b/ts/components/conversation/SystemMessage.tsx @@ -11,7 +11,7 @@ export enum SystemMessageKind { Error = 'Error', } -type PropsType = { +export type PropsType = { icon: string; contents: ReactNode; button?: ReactNode; diff --git a/ts/components/conversation/Timeline.stories.tsx b/ts/components/conversation/Timeline.stories.tsx index 2567a189dd..b8c1e2d8a9 100644 --- a/ts/components/conversation/Timeline.stories.tsx +++ b/ts/components/conversation/Timeline.stories.tsx @@ -4,9 +4,8 @@ import * as React from 'react'; import { times } from 'lodash'; import { v4 as uuid } from 'uuid'; -import { text, boolean, number } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import { setupI18n } from '../../util/setupI18n'; import { DurationInSeconds } from '../../util/durations'; import enMessages from '../../../_locales/en/messages.json'; @@ -32,7 +31,9 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/Timeline', -}; + argTypes: {}, + args: {}, +} satisfies Meta; // eslint-disable-next-line const noop = () => {}; @@ -405,12 +406,11 @@ const renderContactSpoofingReviewDialog = ( return ; }; -const getAbout = () => text('about', '👍 Free to chat'); -const getTitle = () => text('name', 'Cayce Bollard'); -const getProfileName = () => text('profileName', 'Cayce Bollard (profile)'); -const getAvatarPath = () => - text('avatarPath', '/fixtures/kitten-4-112-112.jpg'); -const getPhoneNumber = () => text('phoneNumber', '+1 (808) 555-1234'); +const getAbout = () => '👍 Free to chat'; +const getTitle = () => 'Cayce Bollard'; +const getProfileName = () => 'Cayce Bollard (profile)'; +const getAvatarPath = () => '/fixtures/kitten-4-112-112.jpg'; +const getPhoneNumber = () => '+1 (808) 555-1234'; const renderHeroRow = () => { function Wrapper() { @@ -463,22 +463,17 @@ const useProps = (overrideProps: Partial = {}): PropsType => ({ theme: React.useContext(StorybookThemeContext), getTimestampForMessage: Date.now, - haveNewest: boolean('haveNewest', overrideProps.haveNewest !== false), - haveOldest: boolean('haveOldest', overrideProps.haveOldest !== false), + haveNewest: overrideProps.haveNewest ?? false, + haveOldest: overrideProps.haveOldest ?? false, isConversationSelected: true, - isIncomingMessageRequest: boolean( - 'isIncomingMessageRequest', - overrideProps.isIncomingMessageRequest === true - ), - items: overrideProps.items || Object.keys(items), + isIncomingMessageRequest: overrideProps.isIncomingMessageRequest ?? false, + items: overrideProps.items ?? Object.keys(items), messageChangeCounter: 0, scrollToIndex: overrideProps.scrollToIndex, scrollToIndexCounter: 0, shouldShowMiniPlayer: Boolean(overrideProps.shouldShowMiniPlayer), - totalUnseen: number('totalUnseen', overrideProps.totalUnseen || 0), - oldestUnseenIndex: - number('oldestUnseenIndex', overrideProps.oldestUnseenIndex || 0) || - undefined, + totalUnseen: overrideProps.totalUnseen ?? 0, + oldestUnseenIndex: overrideProps.oldestUnseenIndex ?? 0, invitedContactsForNewlyCreatedGroup: overrideProps.invitedContactsForNewlyCreatedGroup || [], warning: overrideProps.warning, @@ -500,10 +495,6 @@ export function OldestAndNewest(): JSX.Element { return ; } -OldestAndNewest.story = { - name: 'Oldest and Newest', -}; - export function WithActiveMessageRequest(): JSX.Element { const props = useProps({ isIncomingMessageRequest: true, @@ -512,10 +503,6 @@ export function WithActiveMessageRequest(): JSX.Element { return ; } -WithActiveMessageRequest.story = { - name: 'With active message request', -}; - export function WithoutNewestMessage(): JSX.Element { const props = useProps({ haveNewest: false, @@ -533,10 +520,6 @@ export function WithoutNewestMessageActiveMessageRequest(): JSX.Element { return ; } -WithoutNewestMessageActiveMessageRequest.story = { - name: 'Without newest message, active message request', -}; - export function WithoutOldestMessage(): JSX.Element { const props = useProps({ haveOldest: false, @@ -554,10 +537,6 @@ export function EmptyJustHero(): JSX.Element { return ; } -EmptyJustHero.story = { - name: 'Empty (just hero)', -}; - export function LastSeen(): JSX.Element { const props = useProps({ oldestUnseenIndex: 13, @@ -575,10 +554,6 @@ export function TargetIndexToTop(): JSX.Element { return ; } -TargetIndexToTop.story = { - name: 'Target Index to Top', -}; - export function TypingIndicator(): JSX.Element { const props = useProps({ isSomeoneTyping: true }); @@ -602,10 +577,6 @@ export function WithInvitedContactsForANewlyCreatedGroup(): JSX.Element { return ; } -WithInvitedContactsForANewlyCreatedGroup.story = { - name: 'With invited contacts for a newly-created group', -}; - export function WithSameNameInDirectConversationWarning(): JSX.Element { const props = useProps({ warning: { @@ -618,10 +589,6 @@ export function WithSameNameInDirectConversationWarning(): JSX.Element { return ; } -WithSameNameInDirectConversationWarning.story = { - name: 'With "same name in direct conversation" warning', -}; - export function WithSameNameInGroupConversationWarning(): JSX.Element { const props = useProps({ warning: { @@ -638,10 +605,6 @@ export function WithSameNameInGroupConversationWarning(): JSX.Element { return ; } -WithSameNameInGroupConversationWarning.story = { - name: 'With "same name in group conversation" warning', -}; - export function WithJustMiniPlayer(): JSX.Element { const props = useProps({ shouldShowMiniPlayer: true, diff --git a/ts/components/conversation/TimelineFloatingHeader.stories.tsx b/ts/components/conversation/TimelineFloatingHeader.stories.tsx index b3b2983060..c67ca841a4 100644 --- a/ts/components/conversation/TimelineFloatingHeader.stories.tsx +++ b/ts/components/conversation/TimelineFloatingHeader.stories.tsx @@ -2,41 +2,36 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; -import { isBoolean } from 'lodash'; -import { boolean } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; - import type { PropsType } from './TimelineFloatingHeader'; import { TimelineFloatingHeader } from './TimelineFloatingHeader'; const i18n = setupI18n('en', enMessages); -function booleanOr(value: boolean | undefined, defaultValue: boolean): boolean { - return isBoolean(value) ? value : defaultValue; -} - -const createProps = (overrideProps: Partial = {}): PropsType => ({ - isLoading: boolean('isLoading', booleanOr(overrideProps.isLoading, false)), - style: overrideProps.style, - visible: boolean('visible', booleanOr(overrideProps.visible, false)), - i18n, - timestamp: overrideProps.timestamp || Date.now(), -}); - export default { title: 'Components/TimelineFloatingHeader', -}; + argTypes: { + isLoading: { control: { type: 'boolean' } }, + visible: { control: { type: 'boolean' } }, + }, + args: { + isLoading: false, + visible: false, + i18n, + timestamp: Date.now(), + }, +} satisfies Meta; -export function Visible(): JSX.Element { - return ; +export function Default(args: PropsType): JSX.Element { + return ; } -export function Loading(): JSX.Element { - return ( - - ); +export function Visible(args: PropsType): JSX.Element { + return ; +} + +export function Loading(args: PropsType): JSX.Element { + return ; } diff --git a/ts/components/conversation/TimelineItem.stories.tsx b/ts/components/conversation/TimelineItem.stories.tsx index 97fb833ffc..9df960c28a 100644 --- a/ts/components/conversation/TimelineItem.stories.tsx +++ b/ts/components/conversation/TimelineItem.stories.tsx @@ -2,9 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import { EmojiPicker } from '../emoji/EmojiPicker'; import { setupI18n } from '../../util/setupI18n'; import { DurationInSeconds } from '../../util/durations'; @@ -18,6 +17,7 @@ import { getDefaultConversation } from '../../test-both/helpers/getDefaultConver import { WidthBreakpoint } from '../_util'; import { ThemeType } from '../../types/Util'; import { PaymentEventKind } from '../../types/Payment'; +import { ErrorBoundary } from './ErrorBoundary'; const i18n = setupI18n('en', enMessages); @@ -122,7 +122,7 @@ const getDefaultProps = () => ({ export default { title: 'Components/Conversation/TimelineItem', -}; +} satisfies Meta; export function PlainMessage(): JSX.Element { const item = { @@ -584,7 +584,11 @@ export function UnknownType(): JSX.Element { // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any as TimelineItemProps['item']; - return ; + return ( + + + + ); } export function MissingItem(): JSX.Element { diff --git a/ts/components/conversation/TimelineMessage.stories.tsx b/ts/components/conversation/TimelineMessage.stories.tsx index 21d09a3e91..70072d60ef 100644 --- a/ts/components/conversation/TimelineMessage.stories.tsx +++ b/ts/components/conversation/TimelineMessage.stories.tsx @@ -5,8 +5,7 @@ import * as React from 'react'; import { isBoolean, noop } from 'lodash'; import { action } from '@storybook/addon-actions'; -import { boolean, number, select, text } from '@storybook/addon-knobs'; -import type { Meta, Story } from '@storybook/react'; +import type { Meta, StoryFn } from '@storybook/react'; import { SignalService } from '../../protobuf'; import { ConversationColors } from '../../types/Colors'; @@ -68,19 +67,21 @@ export default { argTypes: { conversationType: { control: 'select', - defaultValue: 'direct', options: ['direct', 'group'], }, quote: { control: 'select', - defaultValue: undefined, mapping: quoteOptions, options: Object.keys(quoteOptions), }, }, -} as Meta; + args: { + conversationType: 'direct', + quote: undefined, + }, +} satisfies Meta; -const Template: Story> = args => { +const Template: StoryFn> = args => { return renderBothDirections({ ...createProps(), conversationType: 'direct', @@ -257,13 +258,9 @@ const createProps = (overrideProps: Partial = {}): Props => ({ clearTargetedMessage: action('clearSelectedMessage'), containerElementRef: React.createRef(), containerWidthBreakpoint: WidthBreakpoint.Wide, - conversationColor: - overrideProps.conversationColor || - select('conversationColor', ConversationColors, ConversationColors[0]), - conversationTitle: - overrideProps.conversationTitle || - text('conversationTitle', 'Conversation Title'), - conversationId: text('conversationId', overrideProps.conversationId || ''), + conversationColor: overrideProps.conversationColor ?? ConversationColors[0], + conversationTitle: overrideProps.conversationTitle ?? 'Conversation Title', + conversationId: overrideProps.conversationId ?? '', conversationType: overrideProps.conversationType || 'direct', contact: overrideProps.contact, deletedForEveryone: overrideProps.deletedForEveryone, @@ -272,17 +269,13 @@ const createProps = (overrideProps: Partial = {}): Props => ({ direction: overrideProps.direction || 'incoming', showLightboxForViewOnceMedia: action('showLightboxForViewOnceMedia'), doubleCheckMissingQuoteReference: action('doubleCheckMissingQuoteReference'), - expirationLength: - number('expirationLength', overrideProps.expirationLength || 0) || - undefined, - expirationTimestamp: - number('expirationTimestamp', overrideProps.expirationTimestamp || 0) || - undefined, + expirationLength: overrideProps.expirationLength ?? 0, + expirationTimestamp: overrideProps.expirationTimestamp ?? 0, getPreferredBadge: overrideProps.getPreferredBadge || (() => undefined), giftBadge: overrideProps.giftBadge, i18n, platform: 'darwin', - id: text('id', overrideProps.id || 'random-message-id'), + id: overrideProps.id ?? 'random-message-id', // renderingContext: 'storybook', interactionMode: overrideProps.interactionMode || 'keyboard', isSticker: isBoolean(overrideProps.isSticker) @@ -355,15 +348,15 @@ const createProps = (overrideProps: Partial = {}): Props => ({ showLightbox: action('showLightbox'), startConversation: action('startConversation'), status: overrideProps.status || 'sent', - text: overrideProps.text || text('text', ''), + text: overrideProps.text ?? '', textDirection: overrideProps.textDirection || TextDirection.Default, textAttachment: overrideProps.textAttachment || { contentType: LONG_MESSAGE, size: 123, - pending: boolean('textPending', false), + pending: false, }, theme: ThemeType.light, - timestamp: number('timestamp', overrideProps.timestamp || Date.now()), + timestamp: overrideProps.timestamp ?? Date.now(), viewStory: action('viewStory'), }); @@ -403,9 +396,6 @@ PlainRtlMessage.args = { text: 'الأسانسير، علشان القطط ماتاكلش منها. وننساها، ونعود الى أوراقنا موصدين الباب بإحكام. نتنحنح، ونقول: البتاع. كلمة تدلّ على لا شيء، وعلى كلّ شيء. وهي مركز أبحاث شعبية كثيرة، تتعجّب من غرابتها والقومية المصرية الخاصة التي تعكسها، الى جانب الشيء الكثير من العفوية وحلاوة الروح. نعم، نحن قرأنا وسمعنا وعرفنا كل هذا. لكنه محلّ اهتمامنا اليوم لأسباب غير تلك الأسباب. كذلك، فإننا لعاقدون عزمنا على أن نتجاوز قضية الفصحى والعامية، وثنائية النخبة والرعاع، التي كثيراً ما ينحو نحوها الحديث عن الكلمة المذكورة. وفوق هذا كله، لسنا بصدد تفسير معاني "البتاع" كما تأتي في قصيدة الحاج أحمد فؤاد نجم، ولا التحذلق والتفذلك في الألغاز والأسرار المكنونة. هذا البتاع - أم هذه البت', textDirection: TextDirection.RightToLeft, }; -PlainRtlMessage.story = { - name: 'Plain RTL Message', -}; export function EmojiMessages(): JSX.Element { return ( @@ -538,9 +528,6 @@ WillExpireButStillSending.args = { expirationLength: 30 * 1000, text: 'We always show the timer if a message has an expiration length, even if unread or still sending.', }; -WillExpireButStillSending.story = { - name: 'Will expire but still sending', -}; export const Pending = Template.bind({}); Pending.args = { @@ -564,9 +551,6 @@ LongBodyCanBeDownloaded.args = { key: 'def', }, }; -LongBodyCanBeDownloaded.story = { - name: 'Long body can be downloaded', -}; export const Recent = Template.bind({}); Recent.args = { @@ -658,9 +642,6 @@ ReactionsWiderMessage.args = { }, ], }; -ReactionsWiderMessage.story = { - name: 'Reactions (wider message)', -}; const joyReactions = Array.from({ length: 52 }, () => getJoyReaction()); @@ -734,10 +715,6 @@ ReactionsShortMessage.args = { ], }; -ReactionsShortMessage.story = { - name: 'Reactions (short message)', -}; - export const AvatarInGroup = Template.bind({}); AvatarInGroup.args = { author: getDefaultConversation({ avatarPath: pngUrl }), @@ -745,9 +722,6 @@ AvatarInGroup.args = { status: 'sent', text: 'Hello it is me, the saxophone.', }; -AvatarInGroup.story = { - name: 'Avatar in Group', -}; export const BadgeInGroup = Template.bind({}); BadgeInGroup.args = { @@ -756,9 +730,6 @@ BadgeInGroup.args = { status: 'sent', text: 'Hello it is me, the saxophone.', }; -BadgeInGroup.story = { - name: 'Badge in Group', -}; export const Sticker = Template.bind({}); Sticker.args = { @@ -829,9 +800,6 @@ DeletedWithExpireTimer.args = { expirationTimestamp: Date.now() + 3 * 60 * 1000, status: 'sent', }; -DeletedWithExpireTimer.story = { - name: 'Deleted with expireTimer', -}; export function DeletedWithError(): JSX.Element { const propsPartialError = createProps({ @@ -858,9 +826,6 @@ export function DeletedWithError(): JSX.Element { ); } -DeletedWithError.story = { - name: 'Deleted with error', -}; export const CanDeleteForEveryone = Template.bind({}); CanDeleteForEveryone.args = { @@ -869,9 +834,6 @@ CanDeleteForEveryone.args = { // canDeleteForEveryone: true, direction: 'outgoing', }; -CanDeleteForEveryone.story = { - name: 'Can delete for everyone', -}; export const Error = Template.bind({}); Error.args = { @@ -923,9 +885,6 @@ LinkPreviewInGroup.args = { text: 'Be sure to look at https://www.signal.org', conversationType: 'group', }; -LinkPreviewInGroup.story = { - name: 'Link Preview in Group', -}; export const LinkPreviewWithQuote = Template.bind({}); LinkPreviewWithQuote.args = { @@ -963,9 +922,6 @@ LinkPreviewWithQuote.args = { text: 'Be sure to look at https://www.signal.org', conversationType: 'group', }; -LinkPreviewWithQuote.story = { - name: 'Link Preview with Quote', -}; export const LinkPreviewWithSmallImage = Template.bind({}); LinkPreviewWithSmallImage.args = { @@ -990,9 +946,6 @@ LinkPreviewWithSmallImage.args = { status: 'sent', text: 'Be sure to look at https://www.signal.org', }; -LinkPreviewWithSmallImage.story = { - name: 'Link Preview with Small Image', -}; export const LinkPreviewWithoutImage = Template.bind({}); LinkPreviewWithoutImage.args = { @@ -1010,9 +963,6 @@ LinkPreviewWithoutImage.args = { status: 'sent', text: 'Be sure to look at https://www.signal.org', }; -LinkPreviewWithoutImage.story = { - name: 'Link Preview without Image', -}; export const LinkPreviewWithNoDescription = Template.bind({}); LinkPreviewWithNoDescription.args = { @@ -1028,9 +978,6 @@ LinkPreviewWithNoDescription.args = { status: 'sent', text: 'Be sure to look at https://www.signal.org', }; -LinkPreviewWithNoDescription.story = { - name: 'Link Preview with no description', -}; export const LinkPreviewWithLongDescription = Template.bind({}); LinkPreviewWithLongDescription.args = { @@ -1051,9 +998,6 @@ LinkPreviewWithLongDescription.args = { status: 'sent', text: 'Be sure to look at https://www.signal.org', }; -LinkPreviewWithLongDescription.story = { - name: 'Link Preview with long description', -}; export const LinkPreviewWithSmallImageLongDescription = Template.bind({}); LinkPreviewWithSmallImageLongDescription.args = { @@ -1081,9 +1025,6 @@ LinkPreviewWithSmallImageLongDescription.args = { status: 'sent', text: 'Be sure to look at https://www.signal.org', }; -LinkPreviewWithSmallImageLongDescription.story = { - name: 'Link Preview with small image, long description', -}; export const LinkPreviewWithNoDate = Template.bind({}); LinkPreviewWithNoDate.args = { @@ -1107,9 +1048,6 @@ LinkPreviewWithNoDate.args = { status: 'sent', text: 'Be sure to look at https://www.signal.org', }; -LinkPreviewWithNoDate.story = { - name: 'Link Preview with no date', -}; export const LinkPreviewWithTooNewADate = Template.bind({}); LinkPreviewWithTooNewADate.args = { @@ -1134,9 +1072,6 @@ LinkPreviewWithTooNewADate.args = { status: 'sent', text: 'Be sure to look at https://www.signal.org', }; -LinkPreviewWithTooNewADate.story = { - name: 'Link Preview with too new a date', -}; export function Image(): JSX.Element { const darkImageProps = createProps({ @@ -1312,9 +1247,6 @@ ImageWithCaption.args = { status: 'sent', text: 'This is my home.', }; -ImageWithCaption.story = { - name: 'Image with Caption', -}; export const Gif = Template.bind({}); Gif.args = { @@ -1330,9 +1262,6 @@ Gif.args = { ], status: 'sent', }; -Gif.story = { - name: 'GIF', -}; export const GifInAGroup = Template.bind({}); GifInAGroup.args = { @@ -1349,9 +1278,6 @@ GifInAGroup.args = { conversationType: 'group', status: 'sent', }; -GifInAGroup.story = { - name: 'GIF in a group', -}; export const NotDownloadedGif = Template.bind({}); NotDownloadedGif.args = { @@ -1368,9 +1294,6 @@ NotDownloadedGif.args = { ], status: 'sent', }; -NotDownloadedGif.story = { - name: 'Not Downloaded GIF', -}; export const PendingGif = Template.bind({}); PendingGif.args = { @@ -1388,9 +1311,6 @@ PendingGif.args = { ], status: 'sent', }; -PendingGif.story = { - name: 'Pending GIF', -}; export const _Audio = (): JSX.Element => { function Wrapper() { @@ -1465,9 +1385,6 @@ AudioWithCaption.args = { status: 'sent', text: 'This is what I sound like.', }; -AudioWithCaption.story = { - name: 'Audio with Caption', -}; export const AudioWithNotDownloadedAttachment = Template.bind({}); AudioWithNotDownloadedAttachment.args = { @@ -1479,9 +1396,6 @@ AudioWithNotDownloadedAttachment.args = { ], status: 'sent', }; -AudioWithNotDownloadedAttachment.story = { - name: 'Audio with Not Downloaded Attachment', -}; export const AudioWithPendingAttachment = Template.bind({}); AudioWithPendingAttachment.args = { @@ -1494,9 +1408,6 @@ AudioWithPendingAttachment.args = { ], status: 'sent', }; -AudioWithPendingAttachment.story = { - name: 'Audio with Pending Attachment', -}; export const OtherFileType = Template.bind({}); OtherFileType.args = { @@ -1524,9 +1435,6 @@ OtherFileTypeWithCaption.args = { status: 'sent', text: 'This is what I have done.', }; -OtherFileTypeWithCaption.story = { - name: 'Other File Type with Caption', -}; export const OtherFileTypeWithLongFilename = Template.bind({}); OtherFileTypeWithLongFilename.args = { @@ -1542,9 +1450,6 @@ OtherFileTypeWithLongFilename.args = { status: 'sent', text: 'This is what I have done.', }; -OtherFileTypeWithLongFilename.story = { - name: 'Other File Type with Long Filename', -}; export const TapToViewImage = Template.bind({}); TapToViewImage.args = { @@ -1560,9 +1465,6 @@ TapToViewImage.args = { isTapToView: true, status: 'sent', }; -TapToViewImage.story = { - name: 'TapToView Image', -}; export const TapToViewVideo = Template.bind({}); TapToViewVideo.args = { @@ -1578,9 +1480,6 @@ TapToViewVideo.args = { isTapToView: true, status: 'sent', }; -TapToViewVideo.story = { - name: 'TapToView Video', -}; export const TapToViewGif = Template.bind({}); TapToViewGif.args = { @@ -1597,9 +1496,6 @@ TapToViewGif.args = { isTapToView: true, status: 'sent', }; -TapToViewGif.story = { - name: 'TapToView GIF', -}; export const TapToViewExpired = Template.bind({}); TapToViewExpired.args = { @@ -1616,9 +1512,6 @@ TapToViewExpired.args = { isTapToViewExpired: true, status: 'sent', }; -TapToViewExpired.story = { - name: 'TapToView Expired', -}; export const TapToViewError = Template.bind({}); TapToViewError.args = { @@ -1635,9 +1528,6 @@ TapToViewError.args = { isTapToViewError: true, status: 'sent', }; -TapToViewError.story = { - name: 'TapToView Error', -}; export const DangerousFileType = Template.bind({}); DangerousFileType.args = { @@ -1683,9 +1573,6 @@ Mentions.args = { ], text: '\uFFFC This Is It. The Moment We Should Have Trained For.', }; -Mentions.story = { - name: '@Mentions', -}; export function AllTheContextMenus(): JSX.Element { const props = createProps({ @@ -1706,9 +1593,6 @@ export function AllTheContextMenus(): JSX.Element { return ; } -AllTheContextMenus.story = { - name: 'All the context menus', -}; export const NotApprovedWithLinkPreview = Template.bind({}); NotApprovedWithLinkPreview.args = { @@ -1734,9 +1618,6 @@ NotApprovedWithLinkPreview.args = { text: 'Be sure to look at https://www.signal.org', isMessageRequestAccepted: false, }; -NotApprovedWithLinkPreview.story = { - name: 'Not approved, with link preview', -}; export function CustomColor(): JSX.Element { return ( @@ -1802,10 +1683,6 @@ export const CollapsingTextOnlyDMs = (): JSX.Element => { ]); }; -CollapsingTextOnlyDMs.story = { - name: 'Collapsing text-only DMs', -}; - export const CollapsingTextOnlyGroupMessages = (): JSX.Element => { const author = getDefaultConversation(); @@ -1830,10 +1707,6 @@ export const CollapsingTextOnlyGroupMessages = (): JSX.Element => { ]); }; -CollapsingTextOnlyGroupMessages.story = { - name: 'Collapsing text-only group messages', -}; - export const StoryReply = (): JSX.Element => { const conversation = getDefaultConversation(); @@ -1852,10 +1725,6 @@ export const StoryReply = (): JSX.Element => { }); }; -StoryReply.story = { - name: 'Story reply', -}; - export const StoryReplyYours = (): JSX.Element => { const conversation = getDefaultConversation(); @@ -1874,10 +1743,6 @@ export const StoryReplyYours = (): JSX.Element => { }); }; -StoryReplyYours.story = { - name: 'Story reply (yours)', -}; - export const StoryReplyEmoji = (): JSX.Element => { const conversation = getDefaultConversation(); @@ -1897,10 +1762,6 @@ export const StoryReplyEmoji = (): JSX.Element => { }); }; -StoryReplyEmoji.story = { - name: 'Story reply (emoji)', -}; - const fullContact = { avatar: { avatar: fakeAttachment({ @@ -1935,9 +1796,6 @@ export const EmbeddedContactFullContact = Template.bind({}); EmbeddedContactFullContact.args = { contact: fullContact, }; -EmbeddedContactFullContact.story = { - name: 'EmbeddedContact: Full Contact', -}; export const EmbeddedContactWithSendMessage = Template.bind({}); EmbeddedContactWithSendMessage.args = { @@ -1948,9 +1806,6 @@ EmbeddedContactWithSendMessage.args = { }, direction: 'incoming', }; -EmbeddedContactWithSendMessage.story = { - name: 'EmbeddedContact: with Send Message', -}; export const EmbeddedContactOnlyEmail = Template.bind({}); EmbeddedContactOnlyEmail.args = { @@ -1958,9 +1813,6 @@ EmbeddedContactOnlyEmail.args = { email: fullContact.email, }, }; -EmbeddedContactOnlyEmail.story = { - name: 'EmbeddedContact: Only Email', -}; export const EmbeddedContactGivenName = Template.bind({}); EmbeddedContactGivenName.args = { @@ -1970,9 +1822,6 @@ EmbeddedContactGivenName.args = { }, }, }; -EmbeddedContactGivenName.story = { - name: 'EmbeddedContact: Given Name', -}; export const EmbeddedContactOrganization = Template.bind({}); EmbeddedContactOrganization.args = { @@ -1980,9 +1829,6 @@ EmbeddedContactOrganization.args = { organization: 'Company 5', }, }; -EmbeddedContactOrganization.story = { - name: 'EmbeddedContact: Organization', -}; export const EmbeddedContactGivenFamilyName = Template.bind({}); EmbeddedContactGivenFamilyName.args = { @@ -1993,9 +1839,6 @@ EmbeddedContactGivenFamilyName.args = { }, }, }; -EmbeddedContactGivenFamilyName.story = { - name: 'EmbeddedContact: Given + Family Name', -}; export const EmbeddedContactFamilyName = Template.bind({}); EmbeddedContactFamilyName.args = { @@ -2005,9 +1848,6 @@ EmbeddedContactFamilyName.args = { }, }, }; -EmbeddedContactFamilyName.story = { - name: 'EmbeddedContact: Family Name', -}; export const EmbeddedContactLoadingAvatar = Template.bind({}); EmbeddedContactLoadingAvatar.args = { @@ -2024,9 +1864,6 @@ EmbeddedContactLoadingAvatar.args = { }, }, }; -EmbeddedContactLoadingAvatar.story = { - name: 'EmbeddedContact: Loading Avatar', -}; export const GiftBadgeUnopened = Template.bind({}); GiftBadgeUnopened.args = { @@ -2037,9 +1874,6 @@ GiftBadgeUnopened.args = { state: GiftBadgeStates.Unopened, }, }; -GiftBadgeUnopened.story = { - name: 'Gift Badge: Unopened', -}; const getPreferredBadge = () => ({ category: BadgeCategory.Donor, @@ -2066,9 +1900,6 @@ GiftBadgeRedeemed30Days.args = { state: GiftBadgeStates.Redeemed, }, }; -GiftBadgeRedeemed30Days.story = { - name: 'Gift Badge: Redeemed (30 days)', -}; export const GiftBadgeRedeemed24Hours = Template.bind({}); GiftBadgeRedeemed24Hours.args = { @@ -2080,9 +1911,6 @@ GiftBadgeRedeemed24Hours.args = { state: GiftBadgeStates.Redeemed, }, }; -GiftBadgeRedeemed24Hours.story = { - name: 'Gift Badge: Redeemed (24 hours)', -}; export const GiftBadgeOpened60Minutes = Template.bind({}); GiftBadgeOpened60Minutes.args = { @@ -2094,9 +1922,6 @@ GiftBadgeOpened60Minutes.args = { state: GiftBadgeStates.Opened, }, }; -GiftBadgeOpened60Minutes.story = { - name: 'Gift Badge: Opened (60 minutes)', -}; export const GiftBadgeRedeemed1Minute = Template.bind({}); GiftBadgeRedeemed1Minute.args = { @@ -2108,9 +1933,6 @@ GiftBadgeRedeemed1Minute.args = { state: GiftBadgeStates.Redeemed, }, }; -GiftBadgeRedeemed1Minute.story = { - name: 'Gift Badge: Redeemed (1 minute)', -}; export const GiftBadgeOpenedExpired = Template.bind({}); GiftBadgeOpenedExpired.args = { @@ -2122,9 +1944,6 @@ GiftBadgeOpenedExpired.args = { state: GiftBadgeStates.Opened, }, }; -GiftBadgeOpenedExpired.story = { - name: 'Gift Badge: Opened (expired)', -}; export const GiftBadgeMissingBadge = Template.bind({}); GiftBadgeMissingBadge.args = { @@ -2136,9 +1955,6 @@ GiftBadgeMissingBadge.args = { state: GiftBadgeStates.Redeemed, }, }; -GiftBadgeMissingBadge.story = { - name: 'Gift Badge: Missing Badge', -}; export const PaymentNotification = Template.bind({}); PaymentNotification.args = { diff --git a/ts/components/conversation/TimerNotification.stories.tsx b/ts/components/conversation/TimerNotification.stories.tsx index 25d2f2b07e..3b19c9c298 100644 --- a/ts/components/conversation/TimerNotification.stories.tsx +++ b/ts/components/conversation/TimerNotification.stories.tsx @@ -2,8 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; -import { boolean, number, select, text } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import { setupI18n } from '../../util/setupI18n'; import { DurationInSeconds } from '../../util/durations'; import enMessages from '../../../_locales/en/messages.json'; @@ -14,42 +13,31 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/TimerNotification', -}; - -const createProps = (overrideProps: Partial = {}): Props => ({ - i18n, - type: select( - 'type', - { - fromOther: 'fromOther', - fromMe: 'fromMe', - fromSync: 'fromSync', + argTypes: { + type: { + control: { type: 'select' }, + options: ['fromOther', 'fromMe', 'fromSync'], }, - overrideProps.type || 'fromOther' - ), - title: text('title', overrideProps.title || ''), - ...(boolean('disabled', overrideProps.disabled || false) - ? { - disabled: true, - } - : { - disabled: false, - expireTimer: DurationInSeconds.fromMillis( - number( - 'expireTimer', - ('expireTimer' in overrideProps ? overrideProps.expireTimer : 0) || - 0 - ) - ), - }), -}); + disabled: { control: { type: 'boolean' } }, + expireTimer: { control: { type: 'number' } }, + }, + args: { + i18n, + type: 'fromOther', + title: '', + disabled: false, + expireTimer: DurationInSeconds.fromHours(0), + }, +} satisfies Meta; -export function SetByOther(): JSX.Element { - const props = createProps({ +export function SetByOther(args: Props): JSX.Element { + const props: Props = { + ...args, + disabled: false, expireTimer: DurationInSeconds.fromHours(1), type: 'fromOther', title: 'Mr. Fire', - }); + }; return ( <> @@ -60,14 +48,16 @@ export function SetByOther(): JSX.Element { ); } -export function SetByOtherWithALongName(): JSX.Element { +export function SetByOtherWithALongName(args: Props): JSX.Element { const longName = '🦴🧩📴'.repeat(50); - const props = createProps({ + const props: Props = { + ...args, + disabled: false, expireTimer: DurationInSeconds.fromHours(1), type: 'fromOther', title: longName, - }); + }; return ( <> @@ -78,16 +68,14 @@ export function SetByOtherWithALongName(): JSX.Element { ); } -SetByOtherWithALongName.story = { - name: 'Set By Other (with a long name)', -}; - -export function SetByYou(): JSX.Element { - const props = createProps({ +export function SetByYou(args: Props): JSX.Element { + const props: Props = { + ...args, + disabled: false, expireTimer: DurationInSeconds.fromHours(1), type: 'fromMe', title: 'Mr. Fire', - }); + }; return ( <> @@ -98,12 +86,14 @@ export function SetByYou(): JSX.Element { ); } -export function SetBySync(): JSX.Element { - const props = createProps({ +export function SetBySync(args: Props): JSX.Element { + const props: Props = { + ...args, + disabled: false, expireTimer: DurationInSeconds.fromHours(1), type: 'fromSync', title: 'Mr. Fire', - }); + }; return ( <> diff --git a/ts/components/conversation/TypingAnimation.stories.tsx b/ts/components/conversation/TypingAnimation.stories.tsx index 5d24c54864..714f832fc5 100644 --- a/ts/components/conversation/TypingAnimation.stories.tsx +++ b/ts/components/conversation/TypingAnimation.stories.tsx @@ -2,7 +2,7 @@ // 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 { Props } from './TypingAnimation'; @@ -12,7 +12,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/TypingAnimation', -}; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ i18n, diff --git a/ts/components/conversation/TypingBubble.stories.tsx b/ts/components/conversation/TypingBubble.stories.tsx index 6c892383ab..2b6b093fae 100644 --- a/ts/components/conversation/TypingBubble.stories.tsx +++ b/ts/components/conversation/TypingBubble.stories.tsx @@ -4,8 +4,7 @@ import React, { useEffect, useState } from 'react'; import { times } from 'lodash'; import { action } from '@storybook/addon-actions'; -import { date, select } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import { getDefaultConversation } from '../../test-both/helpers/getDefaultConversation'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; @@ -19,7 +18,9 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/TypingBubble', -}; + argTypes: {}, + args: {}, +} satisfies Meta; const CONTACTS = times(10, index => { const letter = (index + 10).toString(36).toUpperCase(); @@ -52,9 +53,7 @@ const getConversationWithBadges = (id: string) => CONTACTS_WITH_BADGES_BY_ID.get(id) || getDefaultConversation(); const getTypingContactIdTimestamps = (count: number) => - Object.fromEntries( - CONTACT_IDS.slice(0, count).map(id => [id, date('timestamp', new Date())]) - ); + Object.fromEntries(CONTACT_IDS.slice(0, count).map(id => [id, Date.now()])); const createProps = ( overrideProps: Partial = {} @@ -67,13 +66,7 @@ const createProps = ( lastItemTimestamp: undefined, i18n, conversationId: '123', - conversationType: - overrideProps.conversationType || - select( - 'conversationType', - { group: 'group', direct: 'direct' }, - 'direct' - ), + conversationType: overrideProps.conversationType ?? 'direct', getConversation: overrideProps.getConversation || getConversation, getPreferredBadge: badges => badges.length > 0 ? getFakeBadge() : undefined, @@ -109,10 +102,6 @@ export function Group(): JSX.Element { return ; } -Group.story = { - name: 'Group (1 person typing)', -}; - export function GroupStartsTyping(): JSX.Element { const props = createProps({ conversationType: 'group', @@ -132,10 +121,6 @@ export function GroupStartsTyping(): JSX.Element { return ; } -GroupStartsTyping.story = { - name: 'Group (0 to 1 person starts typing)', -}; - export function GroupStoppedTyping(): JSX.Element { const props = createProps({ conversationType: 'group', @@ -152,10 +137,6 @@ export function GroupStoppedTyping(): JSX.Element { return ; } -GroupStoppedTyping.story = { - name: 'Group (1 person stopped typing)', -}; - export function GroupWithBadge(): JSX.Element { const props = createProps({ conversationType: 'group', @@ -166,10 +147,6 @@ export function GroupWithBadge(): JSX.Element { return ; } -GroupWithBadge.story = { - name: 'Group (with badge)', -}; - export function GroupMultiTyping1To2(): JSX.Element { const props = createProps({ conversationType: 'group', @@ -189,10 +166,6 @@ export function GroupMultiTyping1To2(): JSX.Element { return ; } -GroupMultiTyping1To2.story = { - name: 'Group (1 to 2 persons)', -}; - export function GroupMultiTyping2Then1PersonStops(): JSX.Element { const props = createProps({ conversationType: 'group', @@ -212,10 +185,6 @@ export function GroupMultiTyping2Then1PersonStops(): JSX.Element { return ; } -GroupMultiTyping2Then1PersonStops.story = { - name: 'Group (2 persons typing then 1 person stops)', -}; - export function GroupMultiTyping3To4(): JSX.Element { const props = createProps({ conversationType: 'group', @@ -235,10 +204,6 @@ export function GroupMultiTyping3To4(): JSX.Element { return ; } -GroupMultiTyping3To4.story = { - name: 'Group (3 to 4)', -}; - export function GroupMultiTyping10(): JSX.Element { const props = createProps({ conversationType: 'group', @@ -248,10 +213,6 @@ export function GroupMultiTyping10(): JSX.Element { return ; } -GroupMultiTyping10.story = { - name: 'Group (10 persons typing)', -}; - export function GroupMultiTypingWithBadges(): JSX.Element { const props = createProps({ conversationType: 'group', @@ -261,7 +222,3 @@ export function GroupMultiTypingWithBadges(): JSX.Element { return ; } - -GroupMultiTypingWithBadges.story = { - name: 'Group (3 persons typing, 2 persons have badge)', -}; diff --git a/ts/components/conversation/UniversalTimerNotification.stories.tsx b/ts/components/conversation/UniversalTimerNotification.stories.tsx index e6464a9ad5..6c89ffa3e7 100644 --- a/ts/components/conversation/UniversalTimerNotification.stories.tsx +++ b/ts/components/conversation/UniversalTimerNotification.stories.tsx @@ -2,16 +2,16 @@ // SPDX-License-Identifier: AGPL-3.0-only import React from 'react'; - +import type { Meta } from '@storybook/react'; +import type { Props } from './UniversalTimerNotification'; import { UniversalTimerNotification } from './UniversalTimerNotification'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; - import { EXPIRE_TIMERS } from '../../test-both/util/expireTimers'; export default { title: 'Components/UniversalTimerNotification', -}; +} satisfies Meta; const i18n = setupI18n('en', enMessages); diff --git a/ts/components/conversation/UnsupportedMessage.stories.tsx b/ts/components/conversation/UnsupportedMessage.stories.tsx index 5b6f70974a..5af325a46b 100644 --- a/ts/components/conversation/UnsupportedMessage.stories.tsx +++ b/ts/components/conversation/UnsupportedMessage.stories.tsx @@ -2,8 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; -import { boolean, text } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; import type { ContactType, Props } from './UnsupportedMessage'; @@ -13,62 +12,56 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/UnsupportedMessage', -}; + argTypes: { + canProcessNow: { control: { type: 'boolean' } }, + }, + args: { + i18n, + canProcessNow: false, + contact: {} as ContactType, + }, +} satisfies Meta; const createContact = (props: Partial = {}): ContactType => ({ id: '', - title: text('contact title', props.title || ''), - isMe: boolean('contact isMe', props.isMe || false), + title: props.title ?? '', + isMe: props.isMe ?? false, }); -const createProps = (overrideProps: Partial = {}): Props => ({ - i18n, - canProcessNow: boolean('canProcessNow', overrideProps.canProcessNow || false), - contact: overrideProps.contact || ({} as ContactType), -}); - -export function FromSomeone(): JSX.Element { +export function FromSomeone(args: Props): JSX.Element { const contact = createContact({ title: 'Alice', name: 'Alice', }); - const props = createProps({ contact }); - - return ; + return ; } -export function AfterUpgrade(): JSX.Element { +export function AfterUpgrade(args: Props): JSX.Element { const contact = createContact({ title: 'Alice', name: 'Alice', }); - const props = createProps({ contact, canProcessNow: true }); - - return ; + return ; } -export function FromYourself(): JSX.Element { +export function FromYourself(args: Props): JSX.Element { const contact = createContact({ title: 'Alice', name: 'Alice', isMe: true, }); - const props = createProps({ contact }); - - return ; + return ; } -export function FromYourselfAfterUpgrade(): JSX.Element { +export function FromYourselfAfterUpgrade(args: Props): JSX.Element { const contact = createContact({ title: 'Alice', name: 'Alice', isMe: true, }); - const props = createProps({ contact, canProcessNow: true }); - - return ; + return ; } diff --git a/ts/components/conversation/VerificationNotification.stories.tsx b/ts/components/conversation/VerificationNotification.stories.tsx index cafe88c42f..473883c5e5 100644 --- a/ts/components/conversation/VerificationNotification.stories.tsx +++ b/ts/components/conversation/VerificationNotification.stories.tsx @@ -2,8 +2,7 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; -import { boolean } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; import type { Props } from './VerificationNotification'; @@ -11,70 +10,58 @@ import { VerificationNotification } from './VerificationNotification'; const i18n = setupI18n('en', enMessages); -export default { - title: 'Components/Conversation/VerificationNotification', -}; - const contact = { title: 'Mr. Fire' }; -const createProps = (overrideProps: Partial = {}): Props => ({ - i18n, - type: overrideProps.type || 'markVerified', - isLocal: boolean('isLocal', overrideProps.isLocal !== false), - contact: overrideProps.contact || contact, -}); - -export function MarkAsVerified(): JSX.Element { - const props = createProps({ type: 'markVerified' }); - - return ; -} - -MarkAsVerified.story = { - name: 'Mark as Verified', -}; - -export function MarkAsNotVerified(): JSX.Element { - const props = createProps({ type: 'markNotVerified' }); - - return ; -} - -MarkAsNotVerified.story = { - name: 'Mark as Not Verified', -}; - -export function MarkAsVerifiedRemotely(): JSX.Element { - const props = createProps({ type: 'markVerified', isLocal: false }); - - return ; -} - -MarkAsVerifiedRemotely.story = { - name: 'Mark as Verified Remotely', -}; - -export function MarkAsNotVerifiedRemotely(): JSX.Element { - const props = createProps({ type: 'markNotVerified', isLocal: false }); - - return ; -} - -MarkAsNotVerifiedRemotely.story = { - name: 'Mark as Not Verified Remotely', -}; - -export function LongName(): JSX.Element { - const longName = '🎆🍬🏈'.repeat(50); - - const props = createProps({ +export default { + title: 'Components/Conversation/VerificationNotification', + argTypes: { + type: { + control: { + type: 'select', + options: ['markVerified', 'markNotVerified'], + }, + }, + isLocal: { control: { type: 'boolean' } }, + }, + args: { + i18n, type: 'markVerified', - contact: { title: longName }, - }); + isLocal: true, + contact, + }, +} satisfies Meta; - return ; +export function MarkAsVerified(args: Props): JSX.Element { + return ; } -LongName.story = { - name: 'Long name', -}; +export function MarkAsNotVerified(args: Props): JSX.Element { + return ; +} + +export function MarkAsVerifiedRemotely(args: Props): JSX.Element { + return ( + + ); +} + +export function MarkAsNotVerifiedRemotely(args: Props): JSX.Element { + return ( + + ); +} + +export function LongName(args: Props): JSX.Element { + const longName = '🎆🍬🏈'.repeat(50); + return ( + + ); +} diff --git a/ts/components/conversation/conversation-details/AddGroupMembersModal.stories.tsx b/ts/components/conversation/conversation-details/AddGroupMembersModal.stories.tsx index dd24b2463c..4ae78fb697 100644 --- a/ts/components/conversation/conversation-details/AddGroupMembersModal.stories.tsx +++ b/ts/components/conversation/conversation-details/AddGroupMembersModal.stories.tsx @@ -4,9 +4,8 @@ import type { ComponentProps } from 'react'; import React, { useState } from 'react'; import { times } from 'lodash'; - import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import { sleep } from '../../../util/sleep'; import { makeLookup } from '../../../util/makeLookup'; import { deconstructLookup } from '../../../util/deconstructLookup'; @@ -25,7 +24,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ConversationDetails/AddGroupMembersModal', -}; +} satisfies Meta; const allCandidateContacts = times(50, () => getDefaultConversation()); let allCandidateContactsLookup = makeLookup(allCandidateContacts, 'id'); @@ -101,18 +100,10 @@ export function Only3Contacts(): JSX.Element { ); } -Only3Contacts.story = { - name: 'Only 3 contacts', -}; - export function NoCandidateContacts(): JSX.Element { return ; } -NoCandidateContacts.story = { - name: 'No candidate contacts', -}; - export function EveryoneAlreadyAdded(): JSX.Element { return ( ; } - -RequestFailsAfter1Second.story = { - name: 'Request fails after 1 second', -}; diff --git a/ts/components/conversation/conversation-details/ConversationDetails.stories.tsx b/ts/components/conversation/conversation-details/ConversationDetails.stories.tsx index 6b1ddc18c5..3bc57f7a34 100644 --- a/ts/components/conversation/conversation-details/ConversationDetails.stories.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetails.stories.tsx @@ -6,6 +6,7 @@ import * as React from 'react'; import { action } from '@storybook/addon-actions'; import { times } from 'lodash'; +import type { Meta } from '@storybook/react'; import { setupI18n } from '../../../util/setupI18n'; import enMessages from '../../../../_locales/en/messages.json'; import type { Props } from './ConversationDetails'; @@ -29,7 +30,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ConversationDetails/ConversationDetails', -}; +} satisfies Meta; const conversation: ConversationType = getDefaultConversation({ id: '', @@ -145,10 +146,6 @@ export function AsAdmin(): JSX.Element { return ; } -AsAdmin.story = { - name: 'as Admin', -}; - export function AsLastAdmin(): JSX.Element { const props = createProps(); @@ -166,10 +163,6 @@ export function AsLastAdmin(): JSX.Element { ); } -AsLastAdmin.story = { - name: 'as last admin', -}; - export function AsOnlyAdmin(): JSX.Element { const props = createProps(); @@ -189,10 +182,6 @@ export function AsOnlyAdmin(): JSX.Element { ); } -AsOnlyAdmin.story = { - name: 'as only admin', -}; - export function GroupEditable(): JSX.Element { const props = createProps(); @@ -205,10 +194,6 @@ export function GroupEditableWithCustomDisappearingTimeout(): JSX.Element { return ; } -GroupEditableWithCustomDisappearingTimeout.story = { - name: 'Group Editable with custom disappearing timeout', -}; - export function GroupLinksOn(): JSX.Element { const props = createProps(true); @@ -219,10 +204,6 @@ export const _11 = (): JSX.Element => ( ); -_11.story = { - name: '1:1', -}; - function mins(n: number) { return DurationInSeconds.toMillis(DurationInSeconds.fromMinutes(n)); } diff --git a/ts/components/conversation/conversation-details/ConversationDetailsActions.stories.tsx b/ts/components/conversation/conversation-details/ConversationDetailsActions.stories.tsx index ed21a73798..f18681bac5 100644 --- a/ts/components/conversation/conversation-details/ConversationDetailsActions.stories.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetailsActions.stories.tsx @@ -3,9 +3,8 @@ import * as React from 'react'; import { isBoolean } 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 type { Props } from './ConversationDetailsActions'; @@ -16,7 +15,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ConversationDetails/ConversationDetailsActions', -}; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ acceptConversation: action('acceptConversation'), @@ -47,10 +46,6 @@ export function LeftTheGroup(): JSX.Element { return ; } -LeftTheGroup.story = { - name: 'Left the group', -}; - export function BlockedAndLeftTheGroup(): JSX.Element { const props = createProps({ left: true, @@ -61,32 +56,16 @@ export function BlockedAndLeftTheGroup(): JSX.Element { return ; } -BlockedAndLeftTheGroup.story = { - name: 'Blocked and left the group', -}; - export function CannotLeaveBecauseYouAreTheLastAdmin(): JSX.Element { const props = createProps({ cannotLeaveBecauseYouAreLastAdmin: true }); return ; } -CannotLeaveBecauseYouAreTheLastAdmin.story = { - name: 'Cannot leave because you are the last admin', -}; - export const _11 = (): JSX.Element => ( ); -_11.story = { - name: '1:1', -}; - export const _11Blocked = (): JSX.Element => ( ); - -_11Blocked.story = { - name: '1:1 Blocked', -}; diff --git a/ts/components/conversation/conversation-details/ConversationDetailsHeader.stories.tsx b/ts/components/conversation/conversation-details/ConversationDetailsHeader.stories.tsx index c538521412..606fd20247 100644 --- a/ts/components/conversation/conversation-details/ConversationDetailsHeader.stories.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetailsHeader.stories.tsx @@ -2,17 +2,14 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - import { action } from '@storybook/addon-actions'; -import { number, text } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation'; import { getFakeBadges } from '../../../test-both/helpers/getFakeBadge'; import { setupI18n } from '../../../util/setupI18n'; import enMessages from '../../../../_locales/en/messages.json'; import { StorybookThemeContext } from '../../../../.storybook/StorybookThemeContext'; import type { ConversationType } from '../../../state/ducks/conversations'; - import type { Props } from './ConversationDetailsHeader'; import { ConversationDetailsHeader } from './ConversationDetailsHeader'; @@ -21,18 +18,17 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ConversationDetails/ConversationDetailsHeader', -}; + argTypes: {}, + args: {}, +} satisfies Meta; const createConversation = (): ConversationType => getDefaultConversation({ id: '', type: 'group', lastUpdated: 0, - title: text('conversation title', 'Some Conversation'), - groupDescription: text( - 'description', - 'This is a group description. https://www.signal.org' - ), + title: 'Some Conversation', + groupDescription: 'This is a group description. https://www.signal.org', }); function Wrapper(overrideProps: Partial) { @@ -45,7 +41,7 @@ function Wrapper(overrideProps: Partial) { i18n={i18n} canEdit={false} startEditing={action('startEditing')} - memberships={new Array(number('conversation members length', 0))} + memberships={new Array(0)} isGroup isMe={false} theme={theme} @@ -72,10 +68,6 @@ export function BasicNoDescription(): JSX.Element { ); } -BasicNoDescription.story = { - name: 'Basic no-description', -}; - export function EditableNoDescription(): JSX.Element { return ( ( - -); - -_11.story = { - name: '1:1', -}; +export function OneOnOne(): JSX.Element { + return ; +} export function NoteToSelf(): JSX.Element { return ; } - -NoteToSelf.story = { - name: 'Note to self', -}; diff --git a/ts/components/conversation/conversation-details/ConversationDetailsIcon.stories.tsx b/ts/components/conversation/conversation-details/ConversationDetailsIcon.stories.tsx index e48be8cc01..59da2abfb8 100644 --- a/ts/components/conversation/conversation-details/ConversationDetailsIcon.stories.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetailsIcon.stories.tsx @@ -2,15 +2,14 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import type { Props } from './ConversationDetailsIcon'; import { ConversationDetailsIcon, IconType } from './ConversationDetailsIcon'; export default { title: 'Components/Conversation/ConversationDetails/ConversationDetailIcon', -}; +} satisfies Meta; const createProps = (overrideProps: Partial): Props => ({ ariaLabel: overrideProps.ariaLabel || '', diff --git a/ts/components/conversation/conversation-details/ConversationDetailsMediaList.stories.tsx b/ts/components/conversation/conversation-details/ConversationDetailsMediaList.stories.tsx index 41487341be..0ba75c5100 100644 --- a/ts/components/conversation/conversation-details/ConversationDetailsMediaList.stories.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetailsMediaList.stories.tsx @@ -2,26 +2,24 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as 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 { Props } from './ConversationDetailsMediaList'; import { ConversationDetailsMediaList } from './ConversationDetailsMediaList'; +import type { MediaItemType } from '../../../types/MediaItem'; +import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation'; import { createPreparedMediaItems, createRandomMedia, -} from '../media-gallery/AttachmentSection.stories'; -import type { MediaItemType } from '../../../types/MediaItem'; -import { getDefaultConversation } from '../../../test-both/helpers/getDefaultConversation'; +} from '../media-gallery/utils/mocks'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ConversationDetails/ConversationMediaList', -}; +} satisfies Meta; const createProps = (mediaItems?: Array): Props => ({ conversation: getDefaultConversation({ diff --git a/ts/components/conversation/conversation-details/ConversationDetailsMembershipList.stories.tsx b/ts/components/conversation/conversation-details/ConversationDetailsMembershipList.stories.tsx index 099fba8be9..c2a5ef6a0f 100644 --- a/ts/components/conversation/conversation-details/ConversationDetailsMembershipList.stories.tsx +++ b/ts/components/conversation/conversation-details/ConversationDetailsMembershipList.stories.tsx @@ -2,11 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; -import { isBoolean } from 'lodash'; - import { action } from '@storybook/addon-actions'; -import { number } from '@storybook/addon-knobs'; - +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'; @@ -23,14 +20,23 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ConversationDetails/ConversationDetailsMembershipList', -}; + argTypes: {}, + args: { + canAddNewMembers: false, + conversationId: '123', + getPreferredBadge: () => undefined, + i18n, + memberships: [], + showContactModal: action('showContactModal'), + startAddingNewMembers: action('startAddingNewMembers'), + theme: ThemeType.light, + }, +} satisfies Meta; const createMemberships = ( numberOfMemberships = 10 ): Array => { - return Array.from( - new Array(number('number of memberships', numberOfMemberships)) - ).map( + return Array.from(new Array(numberOfMemberships)).map( (_, i): GroupV2Membership => ({ isAdmin: i % 3 === 0, member: getDefaultConversation({ @@ -40,81 +46,52 @@ const createMemberships = ( ); }; -const createProps = (overrideProps: Partial): Props => ({ - canAddNewMembers: isBoolean(overrideProps.canAddNewMembers) - ? overrideProps.canAddNewMembers - : false, - conversationId: '123', - getPreferredBadge: () => undefined, - i18n, - memberships: overrideProps.memberships || [], - showContactModal: action('showContactModal'), - startAddingNewMembers: action('startAddingNewMembers'), - theme: ThemeType.light, -}); - -export function Few(): JSX.Element { +export function Few(args: Props): JSX.Element { const memberships = createMemberships(3); - - const props = createProps({ memberships }); - - return ; + return ( + + ); } -export function Limit(): JSX.Element { +export function Limit(args: Props): JSX.Element { const memberships = createMemberships(5); - - const props = createProps({ memberships }); - - return ; + return ( + + ); } -export function Limit1(): JSX.Element { +export function Limit1(args: Props): JSX.Element { const memberships = createMemberships(6); - - const props = createProps({ memberships }); - - return ; + return ( + + ); } -Limit1.story = { - name: 'Limit +1', -}; - -export function Limit2(): JSX.Element { +export function Limit2(args: Props): JSX.Element { const memberships = createMemberships(7); - - const props = createProps({ memberships }); - - return ; + return ( + + ); } -Limit2.story = { - name: 'Limit +2', -}; - -export function Many(): JSX.Element { +export function Many(args: Props): JSX.Element { const memberships = createMemberships(100); - - const props = createProps({ memberships }); - - return ; + return ( + + ); } -export function None(): JSX.Element { - const props = createProps({ memberships: [] }); - - return ; +export function None(args: Props): JSX.Element { + return ; } -export function CanAddNewMembers(): JSX.Element { +export function CanAddNewMembers(args: Props): JSX.Element { const memberships = createMemberships(10); - - const props = createProps({ canAddNewMembers: true, memberships }); - - return ; + return ( + + ); } - -CanAddNewMembers.story = { - name: 'Can add new members', -}; diff --git a/ts/components/conversation/conversation-details/ConversationNotificationsSettings.stories.tsx b/ts/components/conversation/conversation-details/ConversationNotificationsSettings.stories.tsx index 22c529bae3..511cdd32b5 100644 --- a/ts/components/conversation/conversation-details/ConversationNotificationsSettings.stories.tsx +++ b/ts/components/conversation/conversation-details/ConversationNotificationsSettings.stories.tsx @@ -2,11 +2,11 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as 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 './ConversationNotificationsSettings'; import { ConversationNotificationsSettings } from './ConversationNotificationsSettings'; const i18n = setupI18n('en', enMessages); @@ -14,7 +14,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ConversationDetails/ConversationNotificationsSettings', -}; +} satisfies Meta; const getCommonProps = () => ({ id: 'conversation-id', @@ -30,10 +30,6 @@ export function GroupConversationAllDefault(): JSX.Element { return ; } -GroupConversationAllDefault.story = { - name: 'Group conversation, all default', -}; - export function GroupConversationMuted(): JSX.Element { return ( ); } - -GroupConversationMentionsMuted.story = { - name: 'Group conversation, @mentions muted', -}; diff --git a/ts/components/conversation/conversation-details/ConversationNotificationsSettings.tsx b/ts/components/conversation/conversation-details/ConversationNotificationsSettings.tsx index 2d0b0b243e..2c28cd76e6 100644 --- a/ts/components/conversation/conversation-details/ConversationNotificationsSettings.tsx +++ b/ts/components/conversation/conversation-details/ConversationNotificationsSettings.tsx @@ -14,7 +14,7 @@ import { getMuteOptions } from '../../../util/getMuteOptions'; import { parseIntOrThrow } from '../../../util/parseIntOrThrow'; import { useUniqueId } from '../../../hooks/useUniqueId'; -type PropsType = { +export type PropsType = { id: string; conversationType: ConversationTypeType; dontNotifyForMentionsIfMuted: boolean; diff --git a/ts/components/conversation/conversation-details/EditConversationAttributesModal.stories.tsx b/ts/components/conversation/conversation-details/EditConversationAttributesModal.stories.tsx index 07a13f63cf..d6a5626ab6 100644 --- a/ts/components/conversation/conversation-details/EditConversationAttributesModal.stories.tsx +++ b/ts/components/conversation/conversation-details/EditConversationAttributesModal.stories.tsx @@ -3,9 +3,8 @@ 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 { EditConversationAttributesModal } from './EditConversationAttributesModal'; @@ -16,7 +15,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ConversationDetails/EditConversationAttributesModal', -}; +} satisfies Meta; type PropsType = ComponentProps; @@ -40,10 +39,6 @@ export function NoAvatarEmptyTitle(): JSX.Element { return ; } -NoAvatarEmptyTitle.story = { - name: 'No avatar, empty title', -}; - export function AvatarAndTitle(): JSX.Element { return ( ); } - -HasError.story = { - name: 'Has error', -}; diff --git a/ts/components/conversation/conversation-details/GroupLinkManagement.stories.tsx b/ts/components/conversation/conversation-details/GroupLinkManagement.stories.tsx index 6c34590e23..501a99aa2e 100644 --- a/ts/components/conversation/conversation-details/GroupLinkManagement.stories.tsx +++ b/ts/components/conversation/conversation-details/GroupLinkManagement.stories.tsx @@ -2,9 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as 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 './GroupLinkManagement'; @@ -17,7 +16,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ConversationDetails/GroupLinkManagement', -}; +} satisfies Meta; const AccessControlEnum = Proto.AccessControl.AccessRequired; @@ -61,10 +60,6 @@ export function OffAdmin(): JSX.Element { return ; } -OffAdmin.story = { - name: 'Off (Admin)', -}; - export function OnAdmin(): JSX.Element { const props = createProps( getConversation('https://signal.group/1', AccessControlEnum.ANY), @@ -74,10 +69,6 @@ export function OnAdmin(): JSX.Element { return ; } -OnAdmin.story = { - name: 'On (Admin)', -}; - export function OnAdminAdminApprovalNeeded(): JSX.Element { const props = createProps( getConversation('https://signal.group/1', AccessControlEnum.ADMINISTRATOR), @@ -87,10 +78,6 @@ export function OnAdminAdminApprovalNeeded(): JSX.Element { return ; } -OnAdminAdminApprovalNeeded.story = { - name: 'On (Admin + Admin Approval Needed)', -}; - export function OnNonAdmin(): JSX.Element { const props = createProps( getConversation('https://signal.group/1', AccessControlEnum.ANY) @@ -99,16 +86,8 @@ export function OnNonAdmin(): JSX.Element { return ; } -OnNonAdmin.story = { - name: 'On (Non-admin)', -}; - export function OffNonAdminUserCannotGetHere(): JSX.Element { const props = createProps(undefined, false); return ; } - -OffNonAdminUserCannotGetHere.story = { - name: 'Off (Non-admin) - user cannot get here', -}; diff --git a/ts/components/conversation/conversation-details/GroupV2Permissions.stories.tsx b/ts/components/conversation/conversation-details/GroupV2Permissions.stories.tsx index 60ea837f94..63c23f907c 100644 --- a/ts/components/conversation/conversation-details/GroupV2Permissions.stories.tsx +++ b/ts/components/conversation/conversation-details/GroupV2Permissions.stories.tsx @@ -2,9 +2,8 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as 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 './GroupV2Permissions'; @@ -16,7 +15,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ConversationDetails/GroupV2Permissions', -}; +} satisfies Meta; const conversation: ConversationType = getDefaultConversation({ id: '', @@ -58,10 +57,6 @@ export function NotAdmin(): JSX.Element { ); } -NotAdmin.story = { - name: 'Not admin', -}; - export function AdminButNotAnnouncementReady(): JSX.Element { return ( ); } - -AdminNotAnnouncementReadyButItWasOn.story = { - name: 'Admin, not announcement ready, but it was on', -}; diff --git a/ts/components/conversation/conversation-details/PanelRow.stories.tsx b/ts/components/conversation/conversation-details/PanelRow.stories.tsx index f13e203ba6..c65e2bfc7e 100644 --- a/ts/components/conversation/conversation-details/PanelRow.stories.tsx +++ b/ts/components/conversation/conversation-details/PanelRow.stories.tsx @@ -2,74 +2,66 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - import { action } from '@storybook/addon-actions'; -import { boolean, text } from '@storybook/addon-knobs'; - -import { ConversationDetailsIcon, IconType } from './ConversationDetailsIcon'; +import type { Meta } from '@storybook/react'; import type { Props } from './PanelRow'; import { PanelRow } from './PanelRow'; +import { ConversationDetailsIcon, IconType } from './ConversationDetailsIcon'; export default { title: 'Components/Conversation/ConversationDetails/PanelRow', -}; - -const createProps = (overrideProps: Partial = {}): Props => ({ - icon: boolean('with icon', overrideProps.icon !== undefined) ? ( - - ) : null, - label: text('label', (overrideProps.label as string) || ''), - info: text('info', overrideProps.info || ''), - right: text('right', (overrideProps.right as string) || ''), - actions: boolean('with action', overrideProps.actions !== undefined) ? ( - - ) : null, - onClick: boolean('clickable', overrideProps.onClick !== undefined) - ? overrideProps.onClick || action('onClick') - : undefined, -}); - -export function Basic(): JSX.Element { - const props = createProps({ - label: 'this is a panel row', - }); - - return ; -} - -export function Simple(): JSX.Element { - const props = createProps({ - label: 'this is a panel row', - icon: 'with icon', - right: 'side text', - }); - - return ; -} - -export function Full(): JSX.Element { - const props = createProps({ - label: 'this is a panel row', - icon: 'with icon', - info: 'this is some info that exists below the main label', - right: 'side text', - actions: 'with action', - }); - - return ; -} - -export function Button(): JSX.Element { - const props = createProps({ - label: 'this is a panel row', - icon: 'with icon', - right: 'side text', + argTypes: {}, + args: { + icon: , + label: '', + info: '', + right: '', + actions: ( + + ), onClick: action('onClick'), - }); + }, +} satisfies Meta; - return ; +export function Basic(args: Props): JSX.Element { + return ; +} + +export function Simple(args: Props): JSX.Element { + return ( + + ); +} + +export function Full(args: Props): JSX.Element { + return ( + + ); +} + +export function Button(args: Props): JSX.Element { + return ( + + ); } diff --git a/ts/components/conversation/conversation-details/PanelSection.stories.tsx b/ts/components/conversation/conversation-details/PanelSection.stories.tsx index 437af8106a..eda85a6dbc 100644 --- a/ts/components/conversation/conversation-details/PanelSection.stories.tsx +++ b/ts/components/conversation/conversation-details/PanelSection.stories.tsx @@ -2,65 +2,46 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; - import { action } from '@storybook/addon-actions'; -import { boolean, text } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import type { Props } from './PanelSection'; import { PanelSection } from './PanelSection'; import { PanelRow } from './PanelRow'; export default { title: 'Components/Conversation/ConversationDetails/PanelSection', -}; + argTypes: {}, + args: { + title: 'this is a panel row', + centerTitle: false, + actions: null, + }, +} satisfies Meta; -const createProps = (overrideProps: Partial = {}): Props => ({ - title: text('label', overrideProps.title || ''), - centerTitle: boolean('centerTitle', overrideProps.centerTitle || false), - actions: boolean('with action', overrideProps.actions !== undefined) ? ( - - ) : null, -}); - -export function Basic(): JSX.Element { - const props = createProps({ - title: 'panel section header', - }); - - return ; +export function Basic(args: Props): JSX.Element { + return ; } -export function Centered(): JSX.Element { - const props = createProps({ - title: 'this is a panel row', - centerTitle: true, - }); - - return ; +export function Centered(args: Props): JSX.Element { + return ; } -export function WithActions(): JSX.Element { - const props = createProps({ - title: 'this is a panel row', - actions: ( - - ), - }); - - return ; -} - -export function WithContent(): JSX.Element { - const props = createProps({ - title: 'this is a panel row', - }); - +export function WithActions(args: Props): JSX.Element { return ( - + + action + + } + /> + ); +} + +export function WithContent(args: Props): JSX.Element { + return ( + diff --git a/ts/components/conversation/conversation-details/PendingInvites.stories.tsx b/ts/components/conversation/conversation-details/PendingInvites.stories.tsx index 140acd19c8..069b7a91b0 100644 --- a/ts/components/conversation/conversation-details/PendingInvites.stories.tsx +++ b/ts/components/conversation/conversation-details/PendingInvites.stories.tsx @@ -3,9 +3,8 @@ import * as React from 'react'; import { times } from 'lodash'; - import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; import { generateAci } from '../../../types/ServiceId'; import { StorySendMode } from '../../../types/Stories'; import { setupI18n } from '../../../util/setupI18n'; @@ -21,7 +20,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/ConversationDetails/PendingInvites', -}; +} satisfies Meta; const sortedGroupMembers = Array.from(Array(32)).map((_, i) => i === 0 @@ -90,7 +89,3 @@ export function WithBadges(): JSX.Element { return ; } - -WithBadges.story = { - name: 'With badges', -}; diff --git a/ts/components/conversation/media-gallery/AttachmentSection.stories.tsx b/ts/components/conversation/media-gallery/AttachmentSection.stories.tsx index 5d30e734cd..f69a850f1c 100644 --- a/ts/components/conversation/media-gallery/AttachmentSection.stories.tsx +++ b/ts/components/conversation/media-gallery/AttachmentSection.stories.tsx @@ -4,122 +4,45 @@ /* eslint-disable @typescript-eslint/explicit-module-boundary-types */ import * as React from 'react'; -import { select, text } from '@storybook/addon-knobs'; -import { random, range, sample, sortBy } 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 type { MIMEType } from '../../../types/MIME'; -import type { MediaItemType } from '../../../types/MediaItem'; - import type { Props } from './AttachmentSection'; import { AttachmentSection } from './AttachmentSection'; +import { createRandomDocuments, createRandomMedia, days } from './utils/mocks'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/MediaGallery/AttachmentSection', -}; - -export const now = Date.now(); -const DAY_MS = 24 * 60 * 60 * 1000; -export const days = (n: number) => n * DAY_MS; -const tokens = ['foo', 'bar', 'baz', 'qux', 'quux']; - -const contentTypes = { - gif: 'image/gif', - jpg: 'image/jpeg', - png: 'image/png', - mp4: 'video/mp4', - docx: 'application/text', - pdf: 'application/pdf', - txt: 'application/text', -} as unknown as Record; - -const createRandomFile = ( - startTime: number, - timeWindow: number, - fileExtension: string -): MediaItemType => { - const contentType = contentTypes[fileExtension]; - const fileName = `${sample(tokens)}${sample(tokens)}.${fileExtension}`; - - return { - contentType, - message: { - conversationId: '123', - id: random(now).toString(), - received_at: Math.floor(Math.random() * 10), - received_at_ms: random(startTime, startTime + timeWindow), - attachments: [], - sent_at: Date.now(), + component: AttachmentSection, + argTypes: { + header: { control: { type: 'text' } }, + type: { + control: { + type: 'select', + options: ['media', 'documents'], + }, }, - attachment: { - url: '', - fileName, - size: random(1000, 1000 * 1000 * 50), - contentType, - }, - index: 0, - thumbnailObjectUrl: `https://placekitten.com/${random(50, 150)}/${random( - 50, - 150 - )}`, - }; -}; + }, + args: { + i18n, + header: 'Today', + type: 'media', + mediaItems: [], + onItemClick: action('onItemClick'), + }, +} satisfies Meta; -const createRandomFiles = ( - startTime: number, - timeWindow: number, - fileExtensions: Array -) => - range(random(5, 10)).map(() => - createRandomFile(startTime, timeWindow, sample(fileExtensions) as string) +export function Documents(args: Props) { + const mediaItems = createRandomDocuments(Date.now(), days(1)); + return ( + ); - -export const createRandomDocuments = (startTime: number, timeWindow: number) => - createRandomFiles(startTime, timeWindow, ['docx', 'pdf', 'txt']); - -export const createRandomMedia = (startTime: number, timeWindow: number) => - createRandomFiles(startTime, timeWindow, ['mp4', 'jpg', 'png', 'gif']); - -export const createPreparedMediaItems = ( - fn: typeof createRandomDocuments | typeof createRandomMedia -) => - sortBy( - [ - ...fn(now, days(1)), - ...fn(now - days(1), days(1)), - ...fn(now - days(3), days(3)), - ...fn(now - days(30), days(15)), - ...fn(now - days(365), days(300)), - ], - (item: MediaItemType) => -item.message.received_at - ); - -const createProps = (overrideProps: Partial = {}): Props => ({ - i18n, - header: text('header', 'Today'), - type: select( - 'type', - { media: 'media', documents: 'documents' }, - overrideProps.type || 'media' - ), - mediaItems: overrideProps.mediaItems || [], - onItemClick: action('onItemClick'), -}); - -export function Documents() { - const mediaItems = createRandomDocuments(now, days(1)); - const props = createProps({ mediaItems, type: 'documents' }); - - return ; } -export function Media() { - const mediaItems = createRandomMedia(now, days(1)); - const props = createProps({ mediaItems, type: 'media' }); - - return ; +export function Media(args: Props) { + const mediaItems = createRandomMedia(Date.now(), days(1)); + return ; } diff --git a/ts/components/conversation/media-gallery/DocumentListItem.stories.tsx b/ts/components/conversation/media-gallery/DocumentListItem.stories.tsx index 6e16b96dd0..46646c7807 100644 --- a/ts/components/conversation/media-gallery/DocumentListItem.stories.tsx +++ b/ts/components/conversation/media-gallery/DocumentListItem.stories.tsx @@ -2,25 +2,30 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; -import { boolean, date, number, text } from '@storybook/addon-knobs'; import { action } from '@storybook/addon-actions'; - +import type { Meta } from '@storybook/react'; +import type { Props } from './DocumentListItem'; import { DocumentListItem } from './DocumentListItem'; export default { title: 'Components/Conversation/MediaGallery/DocumentListItem', -}; + argTypes: { + timestamp: { control: { type: 'date' } }, + fileName: { control: { type: 'text' } }, + fileSize: { control: { type: 'number' } }, + shouldShowSeparator: { control: { type: 'boolean' } }, + }, + args: { + timestamp: Date.now(), + fileName: 'meow.jpg', + fileSize: 1024 * 1000 * 2, + shouldShowSeparator: false, + onClick: action('onClick'), + }, +} satisfies Meta; -export function Single(): JSX.Element { - return ( - - ); +export function Single(args: Props): JSX.Element { + return ; } export function Multiple(): JSX.Element { diff --git a/ts/components/conversation/media-gallery/DocumentListItem.tsx b/ts/components/conversation/media-gallery/DocumentListItem.tsx index 77fdc07dc6..8975aebae9 100644 --- a/ts/components/conversation/media-gallery/DocumentListItem.tsx +++ b/ts/components/conversation/media-gallery/DocumentListItem.tsx @@ -7,7 +7,7 @@ import classNames from 'classnames'; import moment from 'moment'; import formatFileSize from 'filesize'; -type Props = { +export type Props = { // Required timestamp: number; diff --git a/ts/components/conversation/media-gallery/EmptyState.stories.tsx b/ts/components/conversation/media-gallery/EmptyState.stories.tsx index a02c7fe5dd..4031462679 100644 --- a/ts/components/conversation/media-gallery/EmptyState.stories.tsx +++ b/ts/components/conversation/media-gallery/EmptyState.stories.tsx @@ -2,13 +2,20 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; -import { text } from '@storybook/addon-knobs'; +import type { Meta } from '@storybook/react'; +import type { Props } from './EmptyState'; import { EmptyState } from './EmptyState'; export default { title: 'Components/Conversation/MediaGallery/EmptyState', -}; + argTypes: { + label: { control: { type: 'text' } }, + }, + args: { + label: 'placeholder text', + }, +} satisfies Meta; -export function Default(): JSX.Element { - return ; +export function Default(args: Props): JSX.Element { + return ; } diff --git a/ts/components/conversation/media-gallery/EmptyState.tsx b/ts/components/conversation/media-gallery/EmptyState.tsx index 4415442c39..56829fb377 100644 --- a/ts/components/conversation/media-gallery/EmptyState.tsx +++ b/ts/components/conversation/media-gallery/EmptyState.tsx @@ -3,7 +3,7 @@ import React from 'react'; -type Props = { +export type Props = { label: string; }; diff --git a/ts/components/conversation/media-gallery/MediaGallery.stories.tsx b/ts/components/conversation/media-gallery/MediaGallery.stories.tsx index 1027f61a76..2c5c9182ee 100644 --- a/ts/components/conversation/media-gallery/MediaGallery.stories.tsx +++ b/ts/components/conversation/media-gallery/MediaGallery.stories.tsx @@ -3,25 +3,23 @@ import * as 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 { Props } from './MediaGallery'; +import { MediaGallery } from './MediaGallery'; import { createPreparedMediaItems, createRandomDocuments, createRandomMedia, days, - now, -} from './AttachmentSection.stories'; -import type { Props } from './MediaGallery'; -import { MediaGallery } from './MediaGallery'; +} from './utils/mocks'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/MediaGallery/MediaGallery', -}; +} satisfies Meta; const createProps = (overrideProps: Partial = {}): Props => ({ conversationId: '123', @@ -34,7 +32,7 @@ const createProps = (overrideProps: Partial = {}): Props => ({ }); export function Populated(): JSX.Element { - const documents = createRandomDocuments(now, days(1)).slice(0, 1); + const documents = createRandomDocuments(Date.now(), days(1)).slice(0, 1); const media = createPreparedMediaItems(createRandomMedia); const props = createProps({ documents, media }); @@ -56,8 +54,8 @@ export function NoMedia(): JSX.Element { } export function OneEach(): JSX.Element { - const media = createRandomMedia(now, days(1)).slice(0, 1); - const documents = createRandomDocuments(now, days(1)).slice(0, 1); + const media = createRandomMedia(Date.now(), days(1)).slice(0, 1); + const documents = createRandomDocuments(Date.now(), days(1)).slice(0, 1); const props = createProps({ documents, media }); diff --git a/ts/components/conversation/media-gallery/MediaGridItem.stories.tsx b/ts/components/conversation/media-gallery/MediaGridItem.stories.tsx index 7f16220442..10672f0bfc 100644 --- a/ts/components/conversation/media-gallery/MediaGridItem.stories.tsx +++ b/ts/components/conversation/media-gallery/MediaGridItem.stories.tsx @@ -3,13 +3,12 @@ import * as 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 { MediaItemType } from '../../../types/MediaItem'; import type { AttachmentType } from '../../../types/Attachment'; import { stringToMIMEType } from '../../../types/MIME'; - import type { Props } from './MediaGridItem'; import { MediaGridItem } from './MediaGridItem'; @@ -17,7 +16,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Conversation/MediaGallery/MediaGridItem', -}; +} satisfies Meta; const createProps = ( overrideProps: Partial & { mediaItem: MediaItemType } @@ -131,7 +130,3 @@ export function OtherContentType(): JSX.Element { return ; } - -OtherContentType.story = { - name: 'Other ContentType', -}; diff --git a/ts/components/conversation/media-gallery/utils/mocks.ts b/ts/components/conversation/media-gallery/utils/mocks.ts new file mode 100644 index 0000000000..4894de4ad8 --- /dev/null +++ b/ts/components/conversation/media-gallery/utils/mocks.ts @@ -0,0 +1,91 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { random, range, sample, sortBy } from 'lodash'; +import type { MIMEType } from '../../../../types/MIME'; +import type { MediaItemType } from '../../../../types/MediaItem'; + +const DAY_MS = 24 * 60 * 60 * 1000; +export const days = (n: number): number => n * DAY_MS; +const tokens = ['foo', 'bar', 'baz', 'qux', 'quux']; + +const contentTypes = { + gif: 'image/gif', + jpg: 'image/jpeg', + png: 'image/png', + mp4: 'video/mp4', + docx: 'application/text', + pdf: 'application/pdf', + txt: 'application/text', +} as unknown as Record; + +function createRandomFile( + startTime: number, + timeWindow: number, + fileExtension: string +): MediaItemType { + const contentType = contentTypes[fileExtension]; + const fileName = `${sample(tokens)}${sample(tokens)}.${fileExtension}`; + + return { + contentType, + message: { + conversationId: '123', + id: random(Date.now()).toString(), + received_at: Math.floor(Math.random() * 10), + received_at_ms: random(startTime, startTime + timeWindow), + attachments: [], + sent_at: Date.now(), + }, + attachment: { + url: '', + fileName, + size: random(1000, 1000 * 1000 * 50), + contentType, + }, + index: 0, + thumbnailObjectUrl: `https://placekitten.com/${random(50, 150)}/${random( + 50, + 150 + )}`, + }; +} + +function createRandomFiles( + startTime: number, + timeWindow: number, + fileExtensions: Array +): Array { + return range(random(5, 10)).map(() => + createRandomFile(startTime, timeWindow, sample(fileExtensions) as string) + ); +} +export function createRandomDocuments( + startTime: number, + timeWindow: number +): Array { + return createRandomFiles(startTime, timeWindow, ['docx', 'pdf', 'txt']); +} + +export function createRandomMedia( + startTime: number, + timeWindow: number +): Array { + return createRandomFiles(startTime, timeWindow, ['mp4', 'jpg', 'png', 'gif']); +} + +export function createPreparedMediaItems( + fn: typeof createRandomDocuments | typeof createRandomMedia +): Array { + const now = Date.now(); + return sortBy( + [ + ...fn(now, days(1)), + ...fn(now - days(1), days(1)), + ...fn(now - days(3), days(3)), + ...fn(now - days(30), days(15)), + ...fn(now - days(365), days(300)), + ], + (item: MediaItemType) => -item.message.received_at + ); +} diff --git a/ts/components/conversationList/MessageSearchResult.stories.tsx b/ts/components/conversationList/MessageSearchResult.stories.tsx index 8401f31cc0..d56e3d5868 100644 --- a/ts/components/conversationList/MessageSearchResult.stories.tsx +++ b/ts/components/conversationList/MessageSearchResult.stories.tsx @@ -3,7 +3,7 @@ import * as 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 { StorybookThemeContext } from '../../../.storybook/StorybookThemeContext'; @@ -23,7 +23,7 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/MessageSearchResult', -}; +} satisfies Meta; const someone = getDefaultConversation({ title: 'Some Person', @@ -85,10 +85,6 @@ export function SenderHasABadge(): JSX.Element { return ; } -SenderHasABadge.story = { - name: 'Sender has a badge', -}; - export function Selected(): JSX.Element { const props = useProps({ from: someone, @@ -118,10 +114,6 @@ export function SearchingInConversation(): JSX.Element { return ; } -SearchingInConversation.story = { - name: 'Searching in Conversation', -}; - export function FromYouToYourself(): JSX.Element { const props = useProps({ from: me, @@ -131,10 +123,6 @@ export function FromYouToYourself(): JSX.Element { return ; } -FromYouToYourself.story = { - name: 'From You to Yourself', -}; - export function FromYouToGroup(): JSX.Element { const props = useProps({ from: me, @@ -144,10 +132,6 @@ export function FromYouToGroup(): JSX.Element { return ; } -FromYouToGroup.story = { - name: 'From You to Group', -}; - export function FromSomeoneToGroup(): JSX.Element { const props = useProps({ from: someone, @@ -157,10 +141,6 @@ export function FromSomeoneToGroup(): JSX.Element { return ; } -FromSomeoneToGroup.story = { - name: 'From Someone to Group', -}; - export function LongSearchResult(): JSX.Element { const snippets = [ 'This is a really <>detail<>ed long line which will wrap and only be cut off after it gets to three lines. So maybe this will make it in as well?', @@ -193,10 +173,6 @@ export function EmptyShouldBeInvalid(): JSX.Element { return ; } -EmptyShouldBeInvalid.story = { - name: 'Empty (should be invalid)', -}; - export function Mention(): JSX.Element { const props = useProps({ body: 'moss banana twine sound lake zoo brain count vacuum work stairs try power forget hair dry diary years no results \uFFFC elephant sorry umbrella potato igloo kangaroo home Georgia bayonet vector orange forge diary zebra turtle rise front \uFFFC', @@ -225,10 +201,6 @@ export function Mention(): JSX.Element { return ; } -Mention.story = { - name: '@mention', -}; - export function MentionRegexp(): JSX.Element { const props = useProps({ body: '\uFFFC This is a (long) /text/ ^$ that is ... specially **crafted** to (test) our regexp escaping mechanism! Making sure that the code we write works in all sorts of scenarios', @@ -250,10 +222,6 @@ export function MentionRegexp(): JSX.Element { return ; } -MentionRegexp.story = { - name: '@mention regexp', -}; - export function MentionNoMatches(): JSX.Element { const props = useProps({ body: '\uFFFC hello', @@ -274,10 +242,6 @@ export function MentionNoMatches(): JSX.Element { return ; } -MentionNoMatches.story = { - name: '@mention no-matches', -}; - export const _MentionNoMatches = (): JSX.Element => { const props = useProps({ body: 'moss banana twine sound lake zoo brain count vacuum work stairs try power forget hair dry diary years no results \uFFFC elephant sorry umbrella potato igloo kangaroo home Georgia bayonet vector orange forge diary zebra turtle rise front \uFFFC', @@ -306,10 +270,6 @@ export const _MentionNoMatches = (): JSX.Element => { return ; }; -_MentionNoMatches.story = { - name: '@mention no-matches', -}; - export function DoubleMention(): JSX.Element { const props = useProps({ body: 'Hey \uFFFC \uFFFC --- test! Two mentions!', @@ -337,10 +297,6 @@ export function DoubleMention(): JSX.Element { return ; } -DoubleMention.story = { - name: 'Double @mention', -}; - export function WithFormatting(): JSX.Element { const props = useProps({ body: "We're playing with formatting in fun ways like you do!", diff --git a/ts/components/emoji/Emoji.stories.tsx b/ts/components/emoji/Emoji.stories.tsx index c61d25229c..403f64ba37 100644 --- a/ts/components/emoji/Emoji.stories.tsx +++ b/ts/components/emoji/Emoji.stories.tsx @@ -2,63 +2,58 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; -import { select, text } from '@storybook/addon-knobs'; +import type { Meta } from '@storybook/react'; import type { Props } from './Emoji'; import { Emoji, EmojiSizes } from './Emoji'; -export default { - title: 'Components/Emoji/Emoji', -}; - const tones = [0, 1, 2, 3, 4, 5]; -const createProps = (overrideProps: Partial = {}): Props => ({ - size: select( - 'size', - EmojiSizes.reduce((m, t) => ({ ...m, [t]: t }), {}), - overrideProps.size || 48 - ), - emoji: text('emoji', overrideProps.emoji || ''), - shortName: text('shortName', overrideProps.shortName || ''), - skinTone: select( - 'skinTone', - tones.reduce((m, t) => ({ ...m, [t]: t }), {}), - overrideProps.skinTone || 0 - ), -}); - -export function Sizes(): JSX.Element { - const props = createProps({ - shortName: 'grinning_face_with_star_eyes', - }); +export default { + title: 'Components/Emoji/Emoji', + argTypes: { + size: { control: { type: 'select' }, options: EmojiSizes }, + emoji: { control: { type: 'text' } }, + shortName: { control: { type: 'text' } }, + skinTone: { control: { type: 'select' }, options: tones }, + }, + args: { + size: 48, + emoji: '', + shortName: '', + skinTone: 0, + }, +} satisfies Meta; +export function Sizes(args: Props): JSX.Element { return ( <> {EmojiSizes.map(size => ( - + ))} ); } -export function SkinTones(): JSX.Element { - const props = createProps({ - shortName: 'raised_back_of_hand', - }); - +export function SkinTones(args: Props): JSX.Element { return ( <> {tones.map(skinTone => ( - + ))} ); } -export function FromEmoji(): JSX.Element { - const props = createProps({ - emoji: '😂', - }); - - return ; +export function FromEmoji(args: Props): JSX.Element { + return ; } diff --git a/ts/components/emoji/EmojiButton.stories.tsx b/ts/components/emoji/EmojiButton.stories.tsx index 7d656e5100..72a890262f 100644 --- a/ts/components/emoji/EmojiButton.stories.tsx +++ b/ts/components/emoji/EmojiButton.stories.tsx @@ -2,19 +2,18 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as 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 { Props } from './EmojiButton'; import { EmojiButton } from './EmojiButton'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/Emoji/EmojiButton', -}; +} satisfies Meta; export function Base(): JSX.Element { return ( diff --git a/ts/components/emoji/EmojiPicker.stories.tsx b/ts/components/emoji/EmojiPicker.stories.tsx index 4014f95a4b..3616aed7e8 100644 --- a/ts/components/emoji/EmojiPicker.stories.tsx +++ b/ts/components/emoji/EmojiPicker.stories.tsx @@ -2,19 +2,18 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as 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 { Props } from './EmojiPicker'; import { EmojiPicker } from './EmojiPicker'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/Emoji/EmojiPicker', -}; +} satisfies Meta; export function Base(): JSX.Element { return ( @@ -88,7 +87,3 @@ export function WithSettingsButton(): JSX.Element { /> ); } - -WithSettingsButton.story = { - name: 'With settings button', -}; diff --git a/ts/components/installScreen/InstallScreenChoosingDeviceNameStep.stories.tsx b/ts/components/installScreen/InstallScreenChoosingDeviceNameStep.stories.tsx index 72d46649dc..5faff62281 100644 --- a/ts/components/installScreen/InstallScreenChoosingDeviceNameStep.stories.tsx +++ b/ts/components/installScreen/InstallScreenChoosingDeviceNameStep.stories.tsx @@ -2,19 +2,18 @@ // SPDX-License-Identifier: AGPL-3.0-only import React, { useState } 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 './InstallScreenChoosingDeviceNameStep'; import { InstallScreenChoosingDeviceNameStep } from './InstallScreenChoosingDeviceNameStep'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/InstallScreen/InstallScreenChoosingDeviceNameStep', -}; +} satisfies Meta; function Wrapper() { const [deviceName, setDeviceName] = useState('Default value'); diff --git a/ts/components/installScreen/InstallScreenChoosingDeviceNameStep.tsx b/ts/components/installScreen/InstallScreenChoosingDeviceNameStep.tsx index 7d5f99bcb7..09b48a7e9c 100644 --- a/ts/components/installScreen/InstallScreenChoosingDeviceNameStep.tsx +++ b/ts/components/installScreen/InstallScreenChoosingDeviceNameStep.tsx @@ -16,7 +16,7 @@ import { InstallScreenSignalLogo } from './InstallScreenSignalLogo'; // DESKTOP-2844. export const MAX_DEVICE_NAME_LENGTH = 50; -type PropsType = { +export type PropsType = { deviceName: string; i18n: LocalizerType; onSubmit: () => void; diff --git a/ts/components/installScreen/InstallScreenErrorStep.stories.tsx b/ts/components/installScreen/InstallScreenErrorStep.stories.tsx index 5566229387..4cddc8307b 100644 --- a/ts/components/installScreen/InstallScreenErrorStep.stories.tsx +++ b/ts/components/installScreen/InstallScreenErrorStep.stories.tsx @@ -2,19 +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 { Props } from './InstallScreenErrorStep'; import { InstallScreenErrorStep, InstallError } from './InstallScreenErrorStep'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/InstallScreen/InstallScreenErrorStep', -}; +} satisfies Meta; const defaultProps = { i18n, @@ -29,26 +28,14 @@ export const _TooManyDevices = (): JSX.Element => ( /> ); -_TooManyDevices.story = { - name: 'Too many devices', -}; - export const _TooOld = (): JSX.Element => ( ); -_TooOld.story = { - name: 'Too old', -}; - export const __TooOld = (): JSX.Element => ( ); -__TooOld.story = { - name: 'Too old', -}; - export const _ConnectionFailed = (): JSX.Element => ( ( /> ); -_ConnectionFailed.story = { - name: 'Connection failed', -}; - export const _UnknownError = (): JSX.Element => ( ); - -_UnknownError.story = { - name: 'Unknown error', -}; diff --git a/ts/components/installScreen/InstallScreenErrorStep.tsx b/ts/components/installScreen/InstallScreenErrorStep.tsx index a4edcdac74..ff1c0184d4 100644 --- a/ts/components/installScreen/InstallScreenErrorStep.tsx +++ b/ts/components/installScreen/InstallScreenErrorStep.tsx @@ -19,17 +19,19 @@ export enum InstallError { QRCodeFailed, } +export type Props = Readonly<{ + error: InstallError; + i18n: LocalizerType; + quit: () => unknown; + tryAgain: () => unknown; +}>; + export function InstallScreenErrorStep({ error, i18n, quit, tryAgain, -}: Readonly<{ - error: InstallError; - i18n: LocalizerType; - quit: () => unknown; - tryAgain: () => unknown; -}>): ReactElement { +}: Props): ReactElement { let errorMessage: string; let buttonText = i18n('icu:installTryAgain'); let onClickButton = () => tryAgain(); diff --git a/ts/components/installScreen/InstallScreenLinkInProgress.stories.tsx b/ts/components/installScreen/InstallScreenLinkInProgress.stories.tsx index 56f94957a8..f42792ec27 100644 --- a/ts/components/installScreen/InstallScreenLinkInProgress.stories.tsx +++ b/ts/components/installScreen/InstallScreenLinkInProgress.stories.tsx @@ -2,17 +2,17 @@ // SPDX-License-Identifier: AGPL-3.0-only import React from 'react'; - +import type { Meta } from '@storybook/react'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; - +import type { Props } from './InstallScreenLinkInProgressStep'; import { InstallScreenLinkInProgressStep } from './InstallScreenLinkInProgressStep'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/InstallScreen/InstallScreenLinkInProgressStep', -}; +} satisfies Meta; export function Default(): JSX.Element { return ; diff --git a/ts/components/installScreen/InstallScreenLinkInProgressStep.tsx b/ts/components/installScreen/InstallScreenLinkInProgressStep.tsx index a56a5fff8e..ebf2c2e361 100644 --- a/ts/components/installScreen/InstallScreenLinkInProgressStep.tsx +++ b/ts/components/installScreen/InstallScreenLinkInProgressStep.tsx @@ -10,9 +10,9 @@ import { Spinner } from '../Spinner'; import { TitlebarDragArea } from '../TitlebarDragArea'; import { InstallScreenSignalLogo } from './InstallScreenSignalLogo'; -export function InstallScreenLinkInProgressStep({ - i18n, -}: Readonly<{ i18n: LocalizerType }>): ReactElement { +export type Props = Readonly<{ i18n: LocalizerType }>; + +export function InstallScreenLinkInProgressStep({ i18n }: Props): ReactElement { return (
diff --git a/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.stories.tsx b/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.stories.tsx index 3610be4d77..95edd97500 100644 --- a/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.stories.tsx +++ b/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.stories.tsx @@ -3,13 +3,13 @@ import React, { useEffect, useState } from 'react'; import { action } from '@storybook/addon-actions'; - +import type { Meta, StoryFn } from '@storybook/react'; import { setupI18n } from '../../util/setupI18n'; import { DialogType } from '../../types/Dialogs'; import enMessages from '../../../_locales/en/messages.json'; - import type { Loadable } from '../../util/loadable'; import { LoadingState } from '../../util/loadable'; +import type { PropsType } from './InstallScreenQrCodeNotScannedStep'; import { InstallScreenQrCodeNotScannedStep } from './InstallScreenQrCodeNotScannedStep'; const i18n = setupI18n('en', enMessages); @@ -32,7 +32,7 @@ const DEFAULT_UPDATES = { export default { title: 'Components/InstallScreen/InstallScreenQrCodeNotScannedStep', argTypes: {}, -}; +} satisfies Meta; function Simulation({ finalResult }: { finalResult: Loadable }) { const [provisioningUrl, setProvisioningUrl] = useState>({ @@ -77,10 +77,6 @@ export function QrCodeLoading(): JSX.Element { ); } -QrCodeLoading.story = { - name: 'QR code loading', -}; - export function QrCodeFailedToLoad(): JSX.Element { return ( ; } -SimulatedLoading.story = { - name: 'Simulated loading', -}; - export function SimulatedFailure(): JSX.Element { return ( = + // eslint-disable-next-line react/function-component-definition + function WithUpdateKnobs({ + dialogType, + currentVersion, + }: { + dialogType: DialogType; + currentVersion: string; + }): JSX.Element { + return ( + + ); + }; -export function WithUpdateKnobs({ - dialogType, - currentVersion, -}: { - dialogType: DialogType; - currentVersion: string; -}): JSX.Element { - return ( - - ); -} - -WithUpdateKnobs.story = { - name: 'With Update Knobs', - argTypes: { - dialogType: { - control: { type: 'select' }, - defaultValue: DialogType.AutoUpdate, - options: Object.values(DialogType), - }, - currentVersion: { - control: { type: 'select' }, - defaultValue: 'v6.0.0', - options: ['v6.0.0', 'v6.1.0-beta.1'], - }, +WithUpdateKnobs.argTypes = { + dialogType: { + control: { type: 'select' }, + options: Object.values(DialogType), + }, + currentVersion: { + control: { type: 'select' }, + options: ['v6.0.0', 'v6.1.0-beta.1'], }, }; +WithUpdateKnobs.args = { + dialogType: DialogType.AutoUpdate, + currentVersion: 'v6.0.0', +}; diff --git a/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.tsx b/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.tsx index 011b561eec..ee60864839 100644 --- a/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.tsx +++ b/ts/components/installScreen/InstallScreenQrCodeNotScannedStep.tsx @@ -23,7 +23,7 @@ import { Environment, getEnvironment } from '../../environment'; // We can't always use destructuring assignment because of the complexity of this props // type. -type PropsType = Readonly<{ +export type PropsType = Readonly<{ i18n: LocalizerType; provisioningUrl: Loadable; hasExpired?: boolean; diff --git a/ts/components/stickers/StickerButton.stories.tsx b/ts/components/stickers/StickerButton.stories.tsx index 827680f321..b8c2ac50f5 100644 --- a/ts/components/stickers/StickerButton.stories.tsx +++ b/ts/components/stickers/StickerButton.stories.tsx @@ -1,11 +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 { action } from '@storybook/addon-actions'; -import { boolean } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; import type { Props } from './StickerButton'; @@ -16,7 +14,7 @@ import { sticker2, tallSticker, wideSticker, -} from './StickerPicker.stories'; +} from './mocks'; const i18n = setupI18n('en', enMessages); @@ -36,8 +34,27 @@ export default { {storyFn()}
), - ] as Array>, -}; + ], + argTypes: { + showIntroduction: { control: { type: 'boolean' } }, + showPickerHint: { control: { type: 'boolean' } }, + }, + args: { + blessedPacks: [], + clearInstalledStickerPack: action('clearInstalledStickerPack'), + clearShowIntroduction: action('clearShowIntroduction'), + clearShowPickerHint: action('clearShowPickerHint'), + i18n, + installedPacks: [], + knownPacks: [], + onClickAddPack: action('onClickAddPack'), + onPickSticker: action('onPickSticker'), + receivedPacks: [], + recentStickers: [], + showIntroduction: false, + showPickerHint: false, + }, +} satisfies Meta; const receivedPacks = [ createPack({ id: 'received-pack-1', status: 'downloaded' }, sticker1), @@ -65,100 +82,61 @@ const knownPacks = [ createPack({ id: 'known-pack-2', status: 'known' }, sticker2), ]; -const createProps = (overrideProps: Partial = {}): Props => ({ - blessedPacks: overrideProps.blessedPacks || [], - clearInstalledStickerPack: action('clearInstalledStickerPack'), - clearShowIntroduction: action('clearShowIntroduction'), - clearShowPickerHint: action('clearShowPickerHint'), - i18n, - installedPack: overrideProps.installedPack, - installedPacks: overrideProps.installedPacks || [], - knownPacks: overrideProps.knownPacks || [], - onClickAddPack: action('onClickAddPack'), - onPickSticker: action('onPickSticker'), - receivedPacks: overrideProps.receivedPacks || [], - recentStickers: [], - showIntroduction: boolean( - 'showIntroduction', - overrideProps.showIntroduction || false - ), - showPickerHint: boolean('showPickerHint', false), -}); - -export function OnlyInstalled(): JSX.Element { - const props = createProps({ installedPacks }); - - return ; +export function OnlyInstalled(args: Props): JSX.Element { + return ; } -export function OnlyReceived(): JSX.Element { - const props = createProps({ receivedPacks }); - - return ; +export function OnlyReceived(args: Props): JSX.Element { + return ; } -export function OnlyKnown(): JSX.Element { - const props = createProps({ knownPacks }); - - return ; +export function OnlyKnown(args: Props): JSX.Element { + return ; } -export function OnlyBlessed(): JSX.Element { - const props = createProps({ blessedPacks }); - - return ; +export function OnlyBlessed(args: Props): JSX.Element { + return ; } -export function NoPacks(): JSX.Element { - const props = createProps(); - - return ; +export function NoPacks(args: Props): JSX.Element { + return ; } -export function InstalledPackTooltip(): JSX.Element { - const props = createProps({ - installedPacks, - installedPack: installedPacks[0], - }); - - return ; +export function InstalledPackTooltip(args: Props): JSX.Element { + return ( + + ); } -export function InstalledPackTooltipWide(): JSX.Element { +export function InstalledPackTooltipWide(args: Props): JSX.Element { const installedPack = createPack({ id: 'installed-pack-wide' }, wideSticker); - const props = createProps({ - installedPacks: [installedPack], - installedPack, - }); - - return ; + return ( + + ); } -InstalledPackTooltipWide.story = { - name: 'Installed Pack Tooltip (Wide)', -}; - -export function InstalledPackTooltipTall(): JSX.Element { +export function InstalledPackTooltipTall(args: Props): JSX.Element { const installedPack = createPack({ id: 'installed-pack-tall' }, tallSticker); - - const props = createProps({ - installedPacks: [installedPack], - installedPack, - }); - - return ; + return ( + + ); } -InstalledPackTooltipTall.story = { - name: 'Installed Pack Tooltip (Tall)', -}; - -export function NewInstallTooltip(): JSX.Element { - const props = createProps({ - installedPacks, - showIntroduction: true, - }); - - return ; +export function NewInstallTooltip(args: Props): JSX.Element { + return ( + + ); } diff --git a/ts/components/stickers/StickerManager.stories.tsx b/ts/components/stickers/StickerManager.stories.tsx index f0067314fe..cba68a01b1 100644 --- a/ts/components/stickers/StickerManager.stories.tsx +++ b/ts/components/stickers/StickerManager.stories.tsx @@ -3,18 +3,18 @@ import * as 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 { Props } from './StickerManager'; import { StickerManager } from './StickerManager'; -import { createPack, sticker1, sticker2 } from './StickerPicker.stories'; +import { createPack, sticker1, sticker2 } from './mocks'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/Stickers/StickerManager', -}; +} satisfies Meta; const receivedPacks = [ createPack({ id: 'received-pack-1', status: 'downloaded' }, sticker1), @@ -72,16 +72,12 @@ export function ReceivedPacks(): JSX.Element { return ; } -export function InstalledKnownPacks(): JSX.Element { +export function InstalledAndKnownPacks(): JSX.Element { const props = createProps({ installedPacks, knownPacks }); return ; } -InstalledKnownPacks.story = { - name: 'Installed + Known Packs', -}; - export function Empty(): JSX.Element { const props = createProps(); diff --git a/ts/components/stickers/StickerPicker.stories.tsx b/ts/components/stickers/StickerPicker.stories.tsx index ac341e07a7..f9dcbf9ec3 100644 --- a/ts/components/stickers/StickerPicker.stories.tsx +++ b/ts/components/stickers/StickerPicker.stories.tsx @@ -2,167 +2,77 @@ // SPDX-License-Identifier: AGPL-3.0-only import * as React from 'react'; -import { sample } from 'lodash'; import { action } from '@storybook/addon-actions'; -import { boolean } from '@storybook/addon-knobs'; - +import type { Meta } from '@storybook/react'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; import type { Props } from './StickerPicker'; import { StickerPicker } from './StickerPicker'; -import type { StickerPackType, StickerType } from '../../state/ducks/stickers'; +import { abeSticker, createPack, packs, recentStickers } from './mocks'; const i18n = setupI18n('en', enMessages); export default { title: 'Components/Stickers/StickerPicker', -}; + component: StickerPicker, + argTypes: { + showPickerHint: { control: { type: 'boolean' } }, + }, + args: { + i18n, + onClickAddPack: action('onClickAddPack'), + onClose: action('onClose'), + onPickSticker: action('onPickSticker'), + packs: [], + recentStickers: [], + showPickerHint: false, + }, +} satisfies Meta; -export const sticker1: StickerType = { - id: 1, - url: '/fixtures/kitten-1-64-64.jpg', - packId: 'foo', - emoji: '', -}; - -export const sticker2: StickerType = { - id: 2, - url: '/fixtures/kitten-2-64-64.jpg', - packId: 'bar', - emoji: '', -}; - -export const sticker3: StickerType = { - id: 3, - url: '/fixtures/kitten-3-64-64.jpg', - packId: 'baz', - emoji: '', -}; - -export const abeSticker: StickerType = { - id: 4, - url: '/fixtures/512x515-thumbs-up-lincoln.webp', - packId: 'abe', - emoji: '', -}; - -export const wideSticker: StickerType = { - id: 5, - url: '/fixtures/1000x50-green.jpeg', - packId: 'wide', - emoji: '', -}; - -export const tallSticker: StickerType = { - id: 6, - url: '/fixtures/50x1000-teal.jpeg', - packId: 'tall', - emoji: '', -}; - -const choosableStickers = [sticker1, sticker2, sticker3, abeSticker]; - -export const createPack = ( - props: Partial, - sticker?: StickerType -): StickerPackType => ({ - id: '', - title: props.id ? `${props.id} title` : 'title', - key: '', - author: '', - isBlessed: false, - lastUsed: 0, - status: 'known', - cover: sticker, - stickerCount: 101, - stickers: sticker - ? Array(101) - .fill(0) - .map((_, id) => ({ ...sticker, id })) - : [], - ...props, -}); - -const packs = [ - createPack({ id: 'tall' }, tallSticker), - createPack({ id: 'wide' }, wideSticker), - ...Array(20) - .fill(0) - .map((_, n) => - createPack({ id: `pack-${n}` }, sample(choosableStickers) as StickerType) - ), -]; - -const recentStickers = [ - abeSticker, - sticker1, - sticker2, - sticker3, - tallSticker, - wideSticker, - { ...sticker2, id: 9999 }, -]; - -const createProps = (overrideProps: Partial = {}): Props => ({ - i18n, - onClickAddPack: action('onClickAddPack'), - onClose: action('onClose'), - onPickSticker: action('onPickSticker'), - packs: overrideProps.packs || [], - recentStickers: overrideProps.recentStickers || [], - showPickerHint: boolean( - 'showPickerHint', - overrideProps.showPickerHint || false - ), -}); - -export function Full(): JSX.Element { - const props = createProps({ packs, recentStickers }); - - return ; +export function Full(args: Props): JSX.Element { + return ( + + ); } -export function PickerHint(): JSX.Element { - const props = createProps({ packs, recentStickers, showPickerHint: true }); - - return ; +export function PickerHint(args: Props): JSX.Element { + return ( + + ); } -export function NoRecentStickers(): JSX.Element { - const props = createProps({ packs }); - - return ; +export function NoRecentStickers(args: Props): JSX.Element { + return ; } -export function Empty(): JSX.Element { - const props = createProps(); - - return ; +export function Empty(args: Props): JSX.Element { + return ; } -export function PendingDownload(): JSX.Element { +export function PendingDownload(args: Props): JSX.Element { const pack = createPack( { status: 'pending', stickers: [abeSticker] }, abeSticker ); - const props = createProps({ packs: [pack] }); - return ; + return ; } -export function Error(): JSX.Element { +export function Error(args: Props): JSX.Element { const pack = createPack( { status: 'error', stickers: [abeSticker] }, abeSticker ); - const props = createProps({ packs: [pack] }); - return ; + return ; } -export function NoCover(): JSX.Element { +export function NoCover(args: Props): JSX.Element { const pack = createPack({ status: 'error', stickers: [abeSticker] }); - const props = createProps({ packs: [pack] }); - - return ; + return ; } diff --git a/ts/components/stickers/StickerPreviewModal.stories.tsx b/ts/components/stickers/StickerPreviewModal.stories.tsx index 59bcb211e4..62025cac7f 100644 --- a/ts/components/stickers/StickerPreviewModal.stories.tsx +++ b/ts/components/stickers/StickerPreviewModal.stories.tsx @@ -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 { Props } from './StickerPreviewModal'; import { StickerPreviewModal } from './StickerPreviewModal'; import { setupI18n } from '../../util/setupI18n'; import enMessages from '../../../_locales/en/messages.json'; @@ -18,7 +18,9 @@ const i18n = setupI18n('en', enMessages); export default { title: 'Components/Stickers/StickerPreviewModal', -}; + argTypes: {}, + args: {}, +} satisfies Meta; const abeSticker = { id: -1, @@ -40,8 +42,8 @@ const tallSticker = { }; export function Full(): JSX.Element { - const title = text('title', 'Foo'); - const author = text('author', 'Foo McBarrington'); + const title = 'Foo'; + const author = 'Foo McBarrington'; const pack = { id: 'foo', @@ -76,8 +78,8 @@ export function Full(): JSX.Element { } export function JustFourStickers(): JSX.Element { - const title = text('title', 'Foo'); - const author = text('author', 'Foo McBarrington'); + const title = 'Foo'; + const author = 'Foo McBarrington'; const pack = { id: 'foo', @@ -104,10 +106,6 @@ export function JustFourStickers(): JSX.Element { ); } -JustFourStickers.story = { - name: 'Just four stickers', -}; - export function InitialDownload(): JSX.Element { return ( ); } - -PackDeleted.story = { - name: 'Pack deleted', -}; diff --git a/ts/components/stickers/mocks.ts b/ts/components/stickers/mocks.ts new file mode 100644 index 0000000000..0aa6a56c69 --- /dev/null +++ b/ts/components/stickers/mocks.ts @@ -0,0 +1,90 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { sample } from 'lodash'; +import type { StickerPackType, StickerType } from '../../state/ducks/stickers'; + +export const sticker1: StickerType = { + id: 1, + url: '/fixtures/kitten-1-64-64.jpg', + packId: 'foo', + emoji: '', +}; + +export const sticker2: StickerType = { + id: 2, + url: '/fixtures/kitten-2-64-64.jpg', + packId: 'bar', + emoji: '', +}; + +export const sticker3: StickerType = { + id: 3, + url: '/fixtures/kitten-3-64-64.jpg', + packId: 'baz', + emoji: '', +}; + +export const abeSticker: StickerType = { + id: 4, + url: '/fixtures/512x515-thumbs-up-lincoln.webp', + packId: 'abe', + emoji: '', +}; + +export const wideSticker: StickerType = { + id: 5, + url: '/fixtures/1000x50-green.jpeg', + packId: 'wide', + emoji: '', +}; + +export const tallSticker: StickerType = { + id: 6, + url: '/fixtures/50x1000-teal.jpeg', + packId: 'tall', + emoji: '', +}; + +const choosableStickers = [sticker1, sticker2, sticker3, abeSticker]; + +export const createPack = ( + props: Partial, + sticker?: StickerType +): StickerPackType => ({ + id: '', + title: props.id ? `${props.id} title` : 'title', + key: '', + author: '', + isBlessed: false, + lastUsed: 0, + status: 'known', + cover: sticker, + stickerCount: 101, + stickers: sticker + ? Array(101) + .fill(0) + .map((_, id) => ({ ...sticker, id })) + : [], + ...props, +}); + +export const packs = [ + createPack({ id: 'tall' }, tallSticker), + createPack({ id: 'wide' }, wideSticker), + ...Array(20) + .fill(0) + .map((_, n) => + createPack({ id: `pack-${n}` }, sample(choosableStickers) as StickerType) + ), +]; + +export const recentStickers = [ + abeSticker, + sticker1, + sticker2, + sticker3, + tallSticker, + wideSticker, + { ...sticker2, id: 9999 }, +]; diff --git a/ts/models/conversations.ts b/ts/models/conversations.ts index 50b15b83b2..8cabc17152 100644 --- a/ts/models/conversations.ts +++ b/ts/models/conversations.ts @@ -362,9 +362,13 @@ export class ConversationModel extends window.Backbone if (sealedSender === undefined) { this.set({ sealedSender: SEALED_SENDER.UNKNOWN }); } + // @ts-expect-error -- Removing legacy prop this.unset('unidentifiedDelivery'); + // @ts-expect-error -- Removing legacy prop this.unset('unidentifiedDeliveryUnrestricted'); + // @ts-expect-error -- Removing legacy prop this.unset('hasFetchedProfile'); + // @ts-expect-error -- Removing legacy prop this.unset('tokens'); this.on('change:members change:membersV2', this.fetchContacts); diff --git a/ts/state/ducks/conversations.ts b/ts/state/ducks/conversations.ts index f9c551f639..727a498712 100644 --- a/ts/state/ducks/conversations.ts +++ b/ts/state/ducks/conversations.ts @@ -162,11 +162,11 @@ import { handleLeaveConversation, } from './composer'; import { ReceiptType } from '../../types/Receipt'; -import { sortByMessageOrder } from '../../util/maybeForwardMessages'; import { Sound, SoundType } from '../../util/Sound'; import { canEditMessage } from '../../util/canEditMessage'; import type { ChangeNavTabActionType } from './nav'; import { CHANGE_NAV_TAB, NavTab, actions as navActions } from './nav'; +import { sortByMessageOrder } from '../../types/ForwardDraft'; // State diff --git a/ts/state/smart/ForwardMessagesModal.tsx b/ts/state/smart/ForwardMessagesModal.tsx index 1f702d86d8..d4b7edff91 100644 --- a/ts/state/smart/ForwardMessagesModal.tsx +++ b/ts/state/smart/ForwardMessagesModal.tsx @@ -20,10 +20,6 @@ import { import { getIntl, getTheme, getRegionCode } from '../selectors/user'; import { getLinkPreview } from '../selectors/linkPreviews'; import { getPreferredBadgeSelector } from '../selectors/badges'; -import type { - ForwardMessageData, - MessageForwardDraft, -} from '../../util/maybeForwardMessages'; import { maybeForwardMessages } from '../../util/maybeForwardMessages'; import { maybeGrabLinkPreview, @@ -37,6 +33,10 @@ import { hydrateRanges } from '../../types/BodyRange'; import { isDownloaded } from '../../types/Attachment'; import { __DEPRECATED$getMessageById } from '../../messages/getMessageById'; import { strictAssert } from '../../util/assert'; +import type { + ForwardMessageData, + MessageForwardDraft, +} from '../../types/ForwardDraft'; function toMessageForwardDraft( props: ForwardMessagePropsType, diff --git a/ts/storybook/types.ts b/ts/storybook/types.ts new file mode 100644 index 0000000000..6af54108e3 --- /dev/null +++ b/ts/storybook/types.ts @@ -0,0 +1,49 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import type { Meta } from '@storybook/react'; +import type { InputType } from '@storybook/types'; + +/** + * Converts all optional properties to required properties that can be set to `undefined`. + */ +type Defined = { + [P in keyof Required]: T[P]; +}; + +/** + * Ensures that the exported meta object from a storybook file has the correct shape. + * + * - `component` is a React component that accepts `Props` + * - `args` has a default value for everything in `Props` + * + * ```ts + * // 1. Always export default an object with the `component`, `argTypes`, and `args` + * export default { + * component: Component, + * argsTypes: { + * propName: { control: { type: "text" } }, + * }, + * args: { + * propName: "defaultValue", + * onEvent: action("onEvent"), + * }, + * + * // 3. Always use `satisfies ComponentMeta`, never use `as` it won't help you + * } satisfies ComponentMeta + * ``` + */ +export type ComponentMeta = Meta & { + /** Ensure we're talking about the right component */ + component: React.ComponentType; + /** Ensure every prop has a default even if its just `undefined` */ + args: Defined; +}; + +export function argPresets(map: Record): InputType { + return { + control: { type: 'select' }, + options: Object.keys(map), + mapping: map, + }; +} diff --git a/ts/test-both/challenge_test.ts b/ts/test-both/challenge_test.ts index a10139e030..93464bcad2 100644 --- a/ts/test-both/challenge_test.ts +++ b/ts/test-both/challenge_test.ts @@ -32,7 +32,7 @@ describe('ChallengeHandler', () => { let challengeStatus = 'idle'; let queuesStarted: Array = []; - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { storage.clear(); challengeStatus = 'idle'; queuesStarted = []; @@ -43,7 +43,7 @@ describe('ChallengeHandler', () => { }); }); - afterEach(function afterEach() { + afterEach(function (this: Mocha.Context) { this.sandbox.restore(); }); @@ -123,7 +123,7 @@ describe('ChallengeHandler', () => { ); }; - it('should automatically start queue after timeout', async function test() { + it('should automatically start queue after timeout', async function (this: Mocha.Context) { const handler = await createHandler(); const one = createChallenge('1'); @@ -138,7 +138,7 @@ describe('ChallengeHandler', () => { assert.isFalse(isInStorage(one.conversationId)); }); - it('should send challenge response', async function test() { + it('should send challenge response', async function (this: Mocha.Context) { const handler = await createHandler({ autoSolve: true }); const one = createChallenge('1', { @@ -154,7 +154,7 @@ describe('ChallengeHandler', () => { assert.equal(challengeStatus, 'idle'); }); - it('should send old challenges', async function test() { + it('should send old challenges', async function (this: Mocha.Context) { const handler = await createHandler(); const challenges = [ @@ -212,7 +212,7 @@ describe('ChallengeHandler', () => { assert.deepEqual(queuesStarted, [one.conversationId]); }); - it('should not retry expired challenges', async function test() { + it('should not retry expired challenges', async function (this: Mocha.Context) { const handler = await createHandler(); const one = createChallenge('1'); @@ -238,7 +238,7 @@ describe('ChallengeHandler', () => { assert.isFalse(isInStorage(one.conversationId)); }); - it('should send challenges that matured while we were offline', async function test() { + it('should send challenges that matured while we were offline', async function (this: Mocha.Context) { const handler = await createHandler(); const one = createChallenge('1'); @@ -265,7 +265,7 @@ describe('ChallengeHandler', () => { assert.equal(challengeStatus, 'idle'); }); - it('should trigger onChallengeSolved', async function test() { + it('should trigger onChallengeSolved', async function (this: Mocha.Context) { const onChallengeSolved = sinon.stub(); const handler = await createHandler({ @@ -284,7 +284,7 @@ describe('ChallengeHandler', () => { sinon.assert.calledOnce(onChallengeSolved); }); - it('should trigger onChallengeFailed', async function test() { + it('should trigger onChallengeFailed', async function (this: Mocha.Context) { const onChallengeFailed = sinon.stub(); const handler = await createHandler({ diff --git a/ts/test-both/util/asyncIterables_test.ts b/ts/test-both/util/asyncIterables_test.ts index e650e4cd3a..5ccd9d3e2c 100644 --- a/ts/test-both/util/asyncIterables_test.ts +++ b/ts/test-both/util/asyncIterables_test.ts @@ -57,7 +57,7 @@ describe('async iterable utilities', () => { }); it('resolves to an array when wrapping an asynchronous iterable', async () => { - const iterable = (async function* test() { + const iterable = (async function* () { yield 1; yield 2; yield 3; diff --git a/ts/test-both/util/iterables_test.ts b/ts/test-both/util/iterables_test.ts index 755d62fd87..a1824fcd71 100644 --- a/ts/test-both/util/iterables_test.ts +++ b/ts/test-both/util/iterables_test.ts @@ -59,7 +59,7 @@ describe('iterable utilities', () => { ); assert.isTrue( isIterable( - (function* generators() { + (function* () { yield 123; })() ) diff --git a/ts/test-both/util/startTimeTravelDetector_test.ts b/ts/test-both/util/startTimeTravelDetector_test.ts index 6b720a0cb7..346cb229b6 100644 --- a/ts/test-both/util/startTimeTravelDetector_test.ts +++ b/ts/test-both/util/startTimeTravelDetector_test.ts @@ -16,7 +16,7 @@ describe('startTimeTravelDetector', () => { sandbox.restore(); }); - it('calls the callback when the time between checks is more than 2 seconds', async function test() { + it('calls the callback when the time between checks is more than 2 seconds', async () => { const callback = sandbox.fake(); startTimeTravelDetector(callback); diff --git a/ts/test-both/util/version_test.ts b/ts/test-both/util/version_test.ts index 7d0bd11435..fa7c115c8b 100644 --- a/ts/test-both/util/version_test.ts +++ b/ts/test-both/util/version_test.ts @@ -74,17 +74,16 @@ describe('version utilities', () => { }); describe('generateAlphaVersion', () => { - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { // This isn't a hook. - // eslint-disable-next-line react-hooks/rules-of-hooks this.clock = useFakeTimers(); }); - afterEach(function afterEach() { + afterEach(function (this: Mocha.Context) { this.clock.restore(); }); - it('uses the current date and provided shortSha', function test() { + it('uses the current date and provided shortSha', function (this: Mocha.Context) { this.clock.setSystemTime(new Date('2021-07-23T01:22:55.692Z').getTime()); const currentVersion = '5.12.0-beta.1'; @@ -96,7 +95,7 @@ describe('version utilities', () => { assert.strictEqual(expected, actual); }); - it('same production version is semver.gt', function test() { + it('same production version is semver.gt', function (this: Mocha.Context) { const currentVersion = '5.12.0-beta.1'; const shortSha = '07f0efc45'; @@ -106,7 +105,7 @@ describe('version utilities', () => { assert.isTrue(semver.gt('5.12.0', actual)); }); - it('same beta version is semver.gt', function test() { + it('same beta version is semver.gt', function (this: Mocha.Context) { const currentVersion = '5.12.0-beta.1'; const shortSha = '07f0efc45'; @@ -116,7 +115,7 @@ describe('version utilities', () => { assert.isTrue(semver.gt(currentVersion, actual)); }); - it('build earlier same day is semver.lt', function test() { + it('build earlier same day is semver.lt', function (this: Mocha.Context) { const currentVersion = '5.12.0-beta.1'; const shortSha = '07f0efc45'; @@ -129,7 +128,7 @@ describe('version utilities', () => { assert.isTrue(semver.lt(actualEarlier, actualLater)); }); - it('build previous day is semver.lt', function test() { + it('build previous day is semver.lt', function (this: Mocha.Context) { const currentVersion = '5.12.0-beta.1'; const shortSha = '07f0efc45'; diff --git a/ts/test-electron/WebsocketResources_test.ts b/ts/test-electron/WebsocketResources_test.ts index fbc54cdf67..cb900e205a 100644 --- a/ts/test-electron/WebsocketResources_test.ts +++ b/ts/test-electron/WebsocketResources_test.ts @@ -29,7 +29,7 @@ describe('WebSocket-Resource', () => { const NOW = Date.now(); - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { this.sandbox = sinon.createSandbox(); this.clock = this.sandbox.useFakeTimers({ now: NOW, @@ -42,7 +42,7 @@ describe('WebSocket-Resource', () => { .callsFake(clearTimeout); }); - afterEach(function afterEach() { + afterEach(function (this: Mocha.Context) { this.sandbox.restore(); }); @@ -145,7 +145,7 @@ describe('WebSocket-Resource', () => { resource.close(); }); - it('force closes the connection', function test(done) { + it('force closes the connection', function (this: Mocha.Context, done) { const socket = new FakeSocket(); const resource = new WebSocketResource(socket as WebSocket, { @@ -161,7 +161,7 @@ describe('WebSocket-Resource', () => { }); describe('with a keepalive config', () => { - it('sends keepalives once a minute', function test(done) { + it('sends keepalives once a minute', function (this: Mocha.Context, done) { const socket = new FakeSocket(); sinon.stub(socket, 'sendBytes').callsFake(data => { @@ -180,7 +180,7 @@ describe('WebSocket-Resource', () => { this.clock.next(); }); - it('optionally disconnects if no response', function thisNeeded1(done) { + it('optionally disconnects if no response', function (this: Mocha.Context, done) { const socket = new FakeSocket(); sinon.stub(socket, 'close').callsFake(() => done()); @@ -197,7 +197,7 @@ describe('WebSocket-Resource', () => { this.clock.next(); }); - it('optionally disconnects if suspended', function thisNeeded1(done) { + it('optionally disconnects if suspended', function (this: Mocha.Context, done) { const socket = new FakeSocket(); sinon.stub(socket, 'close').callsFake(() => done()); @@ -212,7 +212,7 @@ describe('WebSocket-Resource', () => { this.clock.next(); }); - it('allows resetting the keepalive timer', function thisNeeded2(done) { + it('allows resetting the keepalive timer', function (this: Mocha.Context, done) { const startTime = Date.now(); const socket = new FakeSocket(); diff --git a/ts/test-electron/linkPreviews/linkPreviewFetch_test.ts b/ts/test-electron/linkPreviews/linkPreviewFetch_test.ts index a11e1e0beb..462c1827c9 100644 --- a/ts/test-electron/linkPreviews/linkPreviewFetch_test.ts +++ b/ts/test-electron/linkPreviews/linkPreviewFetch_test.ts @@ -841,12 +841,12 @@ describe('link preview fetching', () => { sinon.assert.notCalled(shouldNeverBeCalled); }); - it('stops reading bodies after 1000 kilobytes', async function test() { + it('stops reading bodies after 1000 kilobytes', async () => { const shouldNeverBeCalled = sinon.stub(); const fakeFetch = stub().resolves( makeResponse({ - body: (async function* body() { + body: (async function* () { yield new TextEncoder().encode( 'foo bar' ); diff --git a/ts/test-electron/models/messages_test.ts b/ts/test-electron/models/messages_test.ts index 16e990152c..d62fe65788 100644 --- a/ts/test-electron/models/messages_test.ts +++ b/ts/test-electron/models/messages_test.ts @@ -91,11 +91,11 @@ describe('Message', () => { ); }); - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { this.sandbox = sinon.createSandbox(); }); - afterEach(function afterEach() { + afterEach(function (this: Mocha.Context) { this.sandbox.restore(); }); @@ -103,7 +103,7 @@ describe('Message', () => { describe('send', () => { let oldMessageSender: undefined | MessageSender; - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { oldMessageSender = window.textsecure.messaging; window.textsecure.messaging = @@ -124,7 +124,7 @@ describe('Message', () => { } }); - it('updates `sendStateByConversationId`', async function test() { + it('updates `sendStateByConversationId`', async function (this: Mocha.Context) { this.sandbox.useFakeTimers(1234); const ourConversationId = @@ -681,7 +681,7 @@ describe('Message', () => { ); }); - it("shows a notification's emoji on non-Linux", async function test() { + it("shows a notification's emoji on non-Linux", async function (this: Mocha.Context) { this.sandbox.replace(window.Signal, 'OS', { ...window.Signal.OS, isLinux() { @@ -710,7 +710,7 @@ describe('Message', () => { ); }); - it('hides emoji on Linux', async function test() { + it('hides emoji on Linux', async function (this: Mocha.Context) { this.sandbox.replace(window.Signal, 'OS', { ...window.Signal.OS, isLinux() { diff --git a/ts/test-electron/quill/emoji/completion_test.tsx b/ts/test-electron/quill/emoji/completion_test.tsx index 58d4557d2e..72a6f9cb6d 100644 --- a/ts/test-electron/quill/emoji/completion_test.tsx +++ b/ts/test-electron/quill/emoji/completion_test.tsx @@ -12,7 +12,7 @@ describe('emojiCompletion', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let mockQuill: any; - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { mockQuill = { getLeaf: sinon.stub(), getSelection: sinon.stub(), @@ -56,7 +56,7 @@ describe('emojiCompletion', () => { void >; - beforeEach(function beforeEach() { + beforeEach(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any emojiCompletion.results = [{ short_name: 'joy' } as any]; emojiCompletion.index = 5; @@ -65,12 +65,12 @@ describe('emojiCompletion', () => { .callThrough(); }); - afterEach(function afterEach() { + afterEach(() => { insertEmojiStub.restore(); }); describe('given an emoji is not starting (no colon)', () => { - beforeEach(function beforeEach() { + beforeEach(() => { mockQuill.getSelection.returns({ index: 3, length: 0, @@ -90,7 +90,7 @@ describe('emojiCompletion', () => { }); describe('given a colon in a string (but not an emoji)', () => { - beforeEach(function beforeEach() { + beforeEach(() => { mockQuill.getSelection.returns({ index: 5, length: 0, @@ -110,7 +110,7 @@ describe('emojiCompletion', () => { }); describe('given an emoji is starting but does not have 2 characters', () => { - beforeEach(function beforeEach() { + beforeEach(() => { mockQuill.getSelection.returns({ index: 2, length: 0, @@ -130,7 +130,7 @@ describe('emojiCompletion', () => { }); describe('given an emoji is starting but does not match a short name', () => { - beforeEach(function beforeEach() { + beforeEach(() => { mockQuill.getSelection.returns({ index: 4, length: 0, @@ -150,7 +150,7 @@ describe('emojiCompletion', () => { }); describe('given an emoji is starting and matches short names', () => { - beforeEach(function beforeEach() { + beforeEach(() => { mockQuill.getSelection.returns({ index: 4, length: 0, @@ -171,7 +171,7 @@ describe('emojiCompletion', () => { }); describe('given an emoji was just completed', () => { - beforeEach(function beforeEach() { + beforeEach(() => { mockQuill.getSelection.returns({ index: 7, length: 0, @@ -181,7 +181,7 @@ describe('emojiCompletion', () => { describe('and given it matches a short name', () => { const text = ':smile:'; - beforeEach(function beforeEach() { + beforeEach(() => { const blot = { text, }; @@ -206,7 +206,7 @@ describe('emojiCompletion', () => { describe('and given it matches a short name inside a larger string', () => { const text = 'have a :smile: nice day'; - beforeEach(function beforeEach() { + beforeEach(() => { const blot = { text, }; @@ -242,7 +242,7 @@ describe('emojiCompletion', () => { describe('and given it does not match a short name', () => { const text = ':smyle:'; - beforeEach(function beforeEach() { + beforeEach(() => { const blot = { text, }; @@ -262,7 +262,7 @@ describe('emojiCompletion', () => { const invalidEmoji = ':smyle:'; const middleCursorIndex = validEmoji.length - 3; - beforeEach(function beforeEach() { + beforeEach(() => { mockQuill.getSelection.returns({ index: middleCursorIndex, length: 0, @@ -270,7 +270,7 @@ describe('emojiCompletion', () => { }); describe('and given it matches a short name', () => { - beforeEach(function beforeEach() { + beforeEach(() => { const blot = { text: validEmoji, }; @@ -293,7 +293,7 @@ describe('emojiCompletion', () => { }); describe('and given it does not match a short name', () => { - beforeEach(function beforeEach() { + beforeEach(() => { const blot = { text: invalidEmoji, }; @@ -309,7 +309,7 @@ describe('emojiCompletion', () => { }); describe('given a completeable emoji and colon was just pressed', () => { - beforeEach(function beforeEach() { + beforeEach(() => { mockQuill.getSelection.returns({ index: 6, length: 0, @@ -319,7 +319,7 @@ describe('emojiCompletion', () => { describe('and given it matches a short name', () => { const text = ':smile'; - beforeEach(function beforeEach() { + beforeEach(() => { const blot = { text, }; @@ -349,7 +349,7 @@ describe('emojiCompletion', () => { void >; - beforeEach(function beforeEach() { + beforeEach(() => { emojiCompletion.results = [ // eslint-disable-next-line @typescript-eslint/no-explicit-any { short_name: 'smile' } as any, @@ -364,7 +364,7 @@ describe('emojiCompletion', () => { const text = ':smi'; const index = text.length; - beforeEach(function beforeEach() { + beforeEach(() => { mockQuill.getSelection.returns({ index, length: 0, @@ -391,7 +391,7 @@ describe('emojiCompletion', () => { const text = 'smi'; const index = text.length; - beforeEach(function beforeEach() { + beforeEach(() => { mockQuill.getSelection.returns({ index, length: 0, diff --git a/ts/test-electron/quill/mentions/completion_test.tsx b/ts/test-electron/quill/mentions/completion_test.tsx index 551eb40d60..94097ee0fe 100644 --- a/ts/test-electron/quill/mentions/completion_test.tsx +++ b/ts/test-electron/quill/mentions/completion_test.tsx @@ -74,7 +74,7 @@ describe('MentionCompletion', () => { }; let mentionCompletion: MentionCompletion; - beforeEach(function beforeEach() { + beforeEach(() => { const memberRepositoryRef: MutableRefObject = { current: new MemberRepository(conversations), }; @@ -234,7 +234,7 @@ describe('MentionCompletion', () => { const text = '@Sh'; const index = text.length; - beforeEach(function beforeEach() { + beforeEach(() => { mockQuill.getSelection?.returns({ index }); const blot = { diff --git a/ts/test-electron/scrollUtil_test.ts b/ts/test-electron/scrollUtil_test.ts index fdb1eade74..6664c76ddd 100644 --- a/ts/test-electron/scrollUtil_test.ts +++ b/ts/test-electron/scrollUtil_test.ts @@ -16,7 +16,7 @@ describe('scroll utilities', () => { // These tests to be flaky on Windows CI, sometimes timing out. That doesn't really // make sense because the test is synchronous, but this quick-and-dirty fix is // probably better than a full investigation. - before(function thisNeeded() { + before(function (this: Mocha.Context) { if (process.platform === 'win32') { this.skip(); } diff --git a/ts/test-electron/services/ActiveWindowService_test.ts b/ts/test-electron/services/ActiveWindowService_test.ts index 8d89e15e21..15187f8188 100644 --- a/ts/test-electron/services/ActiveWindowService_test.ts +++ b/ts/test-electron/services/ActiveWindowService_test.ts @@ -10,11 +10,11 @@ import { getActiveWindowService } from '../../services/ActiveWindowService'; describe('ActiveWindowService', () => { const fakeIpcEvent = {}; - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { this.clock = sinon.useFakeTimers({ now: 1000 }); }); - afterEach(function afterEach() { + afterEach(function (this: Mocha.Context) { this.clock.restore(); }); @@ -40,7 +40,7 @@ describe('ActiveWindowService', () => { assert.isTrue(service.isActive()); }); - it('becomes inactive after 15 seconds without interaction', function test() { + it('becomes inactive after 15 seconds without interaction', function (this: Mocha.Context) { const fakeIpc = new EventEmitter(); const service = getActiveWindowService(createFakeDocument(), fakeIpc); @@ -58,7 +58,7 @@ describe('ActiveWindowService', () => { ['click', 'keydown', 'mousedown', 'mousemove', 'touchstart', 'wheel'].forEach( (eventName: string) => { - it(`is inactive even in the face of ${eventName} events if unfocused`, function test() { + it(`is inactive even in the face of ${eventName} events if unfocused`, function (this: Mocha.Context) { const fakeDocument = createFakeDocument(); const fakeIpc = new EventEmitter(); const service = getActiveWindowService(fakeDocument, fakeIpc); @@ -69,7 +69,7 @@ describe('ActiveWindowService', () => { assert.isFalse(service.isActive()); }); - it(`stays active if focused and receiving ${eventName} events`, function test() { + it(`stays active if focused and receiving ${eventName} events`, function (this: Mocha.Context) { const fakeDocument = createFakeDocument(); const fakeIpc = new EventEmitter(); const service = getActiveWindowService(fakeDocument, fakeIpc); @@ -102,7 +102,7 @@ describe('ActiveWindowService', () => { sinon.assert.calledOnce(callback); }); - it('calls callbacks when receiving a click event after being focused', function test() { + it('calls callbacks when receiving a click event after being focused', function (this: Mocha.Context) { const fakeDocument = createFakeDocument(); const fakeIpc = new EventEmitter(); const service = getActiveWindowService(fakeDocument, fakeIpc); @@ -119,7 +119,7 @@ describe('ActiveWindowService', () => { sinon.assert.calledOnce(callback); }); - it('only calls callbacks every 5 seconds; it is throttled', function test() { + it('only calls callbacks every 5 seconds; it is throttled', function (this: Mocha.Context) { const fakeIpc = new EventEmitter(); const service = getActiveWindowService(createFakeDocument(), fakeIpc); diff --git a/ts/test-electron/state/ducks/calling_test.ts b/ts/test-electron/state/ducks/calling_test.ts index e686d0726a..788d5d485f 100644 --- a/ts/test-electron/state/ducks/calling_test.ts +++ b/ts/test-electron/state/ducks/calling_test.ts @@ -186,7 +186,7 @@ describe('calling duck', () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any let oldEvents: any; - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { this.sandbox = sinon.createSandbox(); oldEvents = window.Events; @@ -196,7 +196,7 @@ describe('calling duck', () => { } as any; }); - afterEach(function afterEach() { + afterEach(function (this: Mocha.Context) { this.sandbox.restore(); window.Events = oldEvents; @@ -204,7 +204,7 @@ describe('calling duck', () => { describe('actions', () => { describe('getPresentingSources', () => { - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { this.callingServiceGetPresentingSources = this.sandbox .stub(callingService, 'getPresentingSources') .resolves([ @@ -216,7 +216,7 @@ describe('calling duck', () => { ]); }); - it('retrieves sources from the calling service', async function test() { + it('retrieves sources from the calling service', async function (this: Mocha.Context) { const { getPresentingSources } = actions; const dispatch = sinon.spy(); await getPresentingSources()(dispatch, getEmptyRootState, null); @@ -224,7 +224,7 @@ describe('calling duck', () => { sinon.assert.calledOnce(this.callingServiceGetPresentingSources); }); - it('dispatches SET_PRESENTING_SOURCES', async function test() { + it('dispatches SET_PRESENTING_SOURCES', async () => { const { getPresentingSources } = actions; const dispatch = sinon.spy(); await getPresentingSources()(dispatch, getEmptyRootState, null); @@ -272,14 +272,14 @@ describe('calling duck', () => { }); describe('setPresenting', () => { - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { this.callingServiceSetPresenting = this.sandbox.stub( callingService, 'setPresenting' ); }); - it('calls setPresenting on the calling service', async function test() { + it('calls setPresenting on the calling service', async function (this: Mocha.Context) { const { setPresenting } = actions; const dispatch = sinon.spy(); const presentedSource = { @@ -386,7 +386,7 @@ describe('calling duck', () => { describe('acceptCall', () => { const { acceptCall } = actions; - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { this.callingServiceAccept = this.sandbox .stub(callingService, 'acceptDirectCall') .resolves(); @@ -433,7 +433,7 @@ describe('calling duck', () => { }); }); - it('asks the calling service to accept the call', async function test() { + it('asks the calling service to accept the call', async function (this: Mocha.Context) { const dispatch = sinon.spy(); await acceptCall({ @@ -525,7 +525,7 @@ describe('calling duck', () => { }); }); - it('asks the calling service to join the call', async function test() { + it('asks the calling service to join the call', async function (this: Mocha.Context) { const dispatch = sinon.spy(); await acceptCall({ @@ -585,14 +585,14 @@ describe('calling duck', () => { describe('cancelCall', () => { const { cancelCall } = actions; - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { this.callingServiceStopCallingLobby = this.sandbox.stub( callingService, 'stopCallingLobby' ); }); - it('stops the calling lobby for that conversation', function test() { + it('stops the calling lobby for that conversation', function (this: Mocha.Context) { cancelCall({ conversationId: '123' }); sinon.assert.calledOnce(this.callingServiceStopCallingLobby); @@ -677,7 +677,7 @@ describe('calling duck', () => { let declineDirectCall: sinon.SinonStub; let declineGroupCall: sinon.SinonStub; - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { declineDirectCall = this.sandbox.stub( callingService, 'declineDirectCall' @@ -1269,7 +1269,7 @@ describe('calling duck', () => { describe('peekNotConnectedGroupCall', () => { const { peekNotConnectedGroupCall } = actions; - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { this.callingServicePeekGroupCall = this.sandbox.stub( callingService, 'peekGroupCall' @@ -1439,7 +1439,7 @@ describe('calling duck', () => { describe('setLocalAudio', () => { const { setLocalAudio } = actions; - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { this.callingServiceSetOutgoingAudio = this.sandbox.stub( callingService, 'setOutgoingAudio' @@ -1465,7 +1465,7 @@ describe('calling duck', () => { }); }); - it('updates the outgoing audio for the active call', function test() { + it('updates the outgoing audio for the active call', function (this: Mocha.Context) { const dispatch = sinon.spy(); setLocalAudio({ enabled: false })( @@ -1543,7 +1543,7 @@ describe('calling duck', () => { let rootState: RootStateType; let startCallingLobbyStub: sinon.SinonStub; - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { startCallingLobbyStub = this.sandbox .stub(callingService, 'startCallingLobby') .resolves(); @@ -1913,7 +1913,7 @@ describe('calling duck', () => { describe('startCall', () => { const { startCall } = actions; - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { this.callingStartOutgoingDirectCall = this.sandbox.stub( callingService, 'startOutgoingDirectCall' @@ -1923,7 +1923,7 @@ describe('calling duck', () => { .resolves(); }); - it('asks the calling service to start an outgoing direct call', async function test() { + it('asks the calling service to start an outgoing direct call', async function (this: Mocha.Context) { const dispatch = sinon.spy(); await startCall({ callMode: CallMode.Direct, @@ -1943,7 +1943,7 @@ describe('calling duck', () => { sinon.assert.notCalled(this.callingJoinGroupCall); }); - it('asks the calling service to join a group call', async function test() { + it('asks the calling service to join a group call', async function (this: Mocha.Context) { const dispatch = sinon.spy(); await startCall({ callMode: CallMode.Group, diff --git a/ts/test-electron/state/ducks/stories_test.ts b/ts/test-electron/state/ducks/stories_test.ts index 916382474a..1ada933224 100644 --- a/ts/test-electron/state/ducks/stories_test.ts +++ b/ts/test-electron/state/ducks/stories_test.ts @@ -857,7 +857,7 @@ describe('both/state/ducks/stories', () => { describe('queueStoryDownload', () => { const { queueStoryDownload } = actions; - it('no attachment, no dispatch', async function test() { + it('no attachment, no dispatch', async () => { const storyId = generateUuid(); const messageAttributes = getStoryMessage(storyId); @@ -873,7 +873,7 @@ describe('both/state/ducks/stories', () => { sinon.assert.notCalled(dispatch); }); - it('downloading, no dispatch', async function test() { + it('downloading, no dispatch', async () => { const storyId = generateUuid(); const messageAttributes = { ...getStoryMessage(storyId), @@ -899,7 +899,7 @@ describe('both/state/ducks/stories', () => { sinon.assert.notCalled(dispatch); }); - it('downloaded, no dispatch', async function test() { + it('downloaded, no dispatch', async () => { const storyId = generateUuid(); const messageAttributes = { ...getStoryMessage(storyId), @@ -925,7 +925,7 @@ describe('both/state/ducks/stories', () => { sinon.assert.notCalled(dispatch); }); - it('not downloaded, queued for download', async function test() { + it('not downloaded, queued for download', async () => { const storyId = generateUuid(); const messageAttributes = { ...getStoryMessage(storyId), @@ -974,7 +974,7 @@ describe('both/state/ducks/stories', () => { }); }); - it('preview not downloaded, queued for download', async function test() { + it('preview not downloaded, queued for download', async () => { const storyId = generateUuid(); const preview = { url: 'https://signal.org', diff --git a/ts/test-electron/textsecure/generate_keys_test.ts b/ts/test-electron/textsecure/generate_keys_test.ts index 20698e9e9c..9238a1e3e6 100644 --- a/ts/test-electron/textsecure/generate_keys_test.ts +++ b/ts/test-electron/textsecure/generate_keys_test.ts @@ -17,7 +17,7 @@ const assertEqualBuffers = (a: Uint8Array, b: Uint8Array) => { assert.isTrue(constantTimeEqual(a, b)); }; -describe('Key generation', function thisNeeded() { +describe('Key generation', function (this: Mocha.Suite) { const count = 10; const ourServiceId = normalizeAci( 'aaaaaaaa-bbbb-4ccc-9ddd-eeeeeeeeeeee', diff --git a/ts/test-mock/messaging/edit_test.ts b/ts/test-mock/messaging/edit_test.ts index 2165725e71..620d40033b 100644 --- a/ts/test-mock/messaging/edit_test.ts +++ b/ts/test-mock/messaging/edit_test.ts @@ -54,7 +54,7 @@ function createEditedMessage( }; } -describe('editing', function needsName() { +describe('editing', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -67,7 +67,7 @@ describe('editing', function needsName() { app = await bootstrap.link(); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { if (!bootstrap) { return; } diff --git a/ts/test-mock/messaging/readSync_test.ts b/ts/test-mock/messaging/readSync_test.ts index db96d98cac..4d19e12815 100644 --- a/ts/test-mock/messaging/readSync_test.ts +++ b/ts/test-mock/messaging/readSync_test.ts @@ -11,7 +11,7 @@ import { Bootstrap } from '../bootstrap'; export const debug = createDebug('mock:test:readSync'); -describe('readSync', function needsName() { +describe('readSync', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -24,7 +24,7 @@ describe('readSync', function needsName() { app = await bootstrap.link(); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { if (!bootstrap) { return; } diff --git a/ts/test-mock/messaging/sender_key_test.ts b/ts/test-mock/messaging/sender_key_test.ts index 14e8810857..d837c6f06e 100644 --- a/ts/test-mock/messaging/sender_key_test.ts +++ b/ts/test-mock/messaging/sender_key_test.ts @@ -11,7 +11,7 @@ import { Bootstrap } from '../bootstrap'; export const debug = createDebug('mock:test:senderKey'); -describe('senderKey', function needsName() { +describe('senderKey', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -50,7 +50,7 @@ describe('senderKey', function needsName() { app = await bootstrap.link(); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { if (!bootstrap) { return; } diff --git a/ts/test-mock/messaging/stories_test.ts b/ts/test-mock/messaging/stories_test.ts index 926b0b4166..660462606c 100644 --- a/ts/test-mock/messaging/stories_test.ts +++ b/ts/test-mock/messaging/stories_test.ts @@ -20,7 +20,7 @@ const IdentifierType = Proto.ManifestRecord.Identifier.Type; const DISTRIBUTION1 = generateStoryDistributionId(); const DISTRIBUTION2 = generateStoryDistributionId(); -describe('story/messaging', function unknownContacts() { +describe('story/messaging', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -114,7 +114,7 @@ describe('story/messaging', function unknownContacts() { app = await bootstrap.link(); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { if (!bootstrap) { return; } diff --git a/ts/test-mock/messaging/unknown_contact_test.ts b/ts/test-mock/messaging/unknown_contact_test.ts index 38e453ebda..b47c019135 100644 --- a/ts/test-mock/messaging/unknown_contact_test.ts +++ b/ts/test-mock/messaging/unknown_contact_test.ts @@ -12,7 +12,7 @@ import { Bootstrap } from '../bootstrap'; export const debug = createDebug('mock:test:edit'); -describe('unknown contacts', function unknownContacts() { +describe('unknown contacts', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -36,7 +36,7 @@ describe('unknown contacts', function unknownContacts() { await unknownContact.addSingleUseKey(desktop, ourKey); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { if (!bootstrap) { return; } diff --git a/ts/test-mock/pnp/accept_gv2_invite_test.ts b/ts/test-mock/pnp/accept_gv2_invite_test.ts index e10817a8ec..4f07575575 100644 --- a/ts/test-mock/pnp/accept_gv2_invite_test.ts +++ b/ts/test-mock/pnp/accept_gv2_invite_test.ts @@ -12,7 +12,7 @@ import type { App } from '../bootstrap'; export const debug = createDebug('mock:test:gv2'); -describe('pnp/accept gv2 invite', function needsName() { +describe('pnp/accept gv2 invite', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -56,7 +56,7 @@ describe('pnp/accept gv2 invite', function needsName() { await leftPane.locator(`[data-testid="${group.id}"]`).click(); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { await bootstrap.maybeSaveLogs(this.currentTest, app); await app.close(); await bootstrap.teardown(); diff --git a/ts/test-mock/pnp/change_number_test.ts b/ts/test-mock/pnp/change_number_test.ts index 1871c6e5ca..8c8c8bdf46 100644 --- a/ts/test-mock/pnp/change_number_test.ts +++ b/ts/test-mock/pnp/change_number_test.ts @@ -10,7 +10,7 @@ import type { App } from '../bootstrap'; export const debug = createDebug('mock:test:change-number'); -describe('pnp/change number', function needsName() { +describe('pnp/change number', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -23,7 +23,7 @@ describe('pnp/change number', function needsName() { app = await bootstrap.link(); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { await bootstrap.maybeSaveLogs(this.currentTest, app); await app.close(); await bootstrap.teardown(); diff --git a/ts/test-mock/pnp/merge_test.ts b/ts/test-mock/pnp/merge_test.ts index 4f87c70dd2..a73c630477 100644 --- a/ts/test-mock/pnp/merge_test.ts +++ b/ts/test-mock/pnp/merge_test.ts @@ -18,7 +18,7 @@ export const debug = createDebug('mock:test:merge'); const IdentifierType = Proto.ManifestRecord.Identifier.Type; -describe('pnp/merge', function needsName() { +describe('pnp/merge', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -93,7 +93,7 @@ describe('pnp/merge', function needsName() { app = await bootstrap.link(); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { await bootstrap.maybeSaveLogs(this.currentTest, app); await app.close(); await bootstrap.teardown(); diff --git a/ts/test-mock/pnp/pni_change_test.ts b/ts/test-mock/pnp/pni_change_test.ts index 2fd6e5095d..d671841133 100644 --- a/ts/test-mock/pnp/pni_change_test.ts +++ b/ts/test-mock/pnp/pni_change_test.ts @@ -13,7 +13,7 @@ import type { App } from '../bootstrap'; export const debug = createDebug('mock:test:pni-change'); -describe('pnp/PNI Change', function needsName() { +describe('pnp/PNI Change', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -62,7 +62,7 @@ describe('pnp/PNI Change', function needsName() { app = await bootstrap.link(); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { await bootstrap.maybeSaveLogs(this.currentTest, app); await app.close(); await bootstrap.teardown(); diff --git a/ts/test-mock/pnp/pni_signature_test.ts b/ts/test-mock/pnp/pni_signature_test.ts index 22a22d3c19..c91d9458d1 100644 --- a/ts/test-mock/pnp/pni_signature_test.ts +++ b/ts/test-mock/pnp/pni_signature_test.ts @@ -23,7 +23,7 @@ export const debug = createDebug('mock:test:pni-signature'); const IdentifierType = Proto.ManifestRecord.Identifier.Type; -describe('pnp/PNI Signature', function needsName() { +describe('pnp/PNI Signature', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -88,7 +88,7 @@ describe('pnp/PNI Signature', function needsName() { app = await bootstrap.link(); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { await bootstrap.maybeSaveLogs(this.currentTest, app); await app.close(); await bootstrap.teardown(); diff --git a/ts/test-mock/pnp/pni_unlink_test.ts b/ts/test-mock/pnp/pni_unlink_test.ts index 1fcebe0ec8..dd92249dd6 100644 --- a/ts/test-mock/pnp/pni_unlink_test.ts +++ b/ts/test-mock/pnp/pni_unlink_test.ts @@ -18,7 +18,7 @@ import type { App } from '../bootstrap'; export const debug = createDebug('mock:test:pni-unlink'); -describe('pnp/PNI DecryptionError unlink', function needsName() { +describe('pnp/PNI DecryptionError unlink', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -34,7 +34,7 @@ describe('pnp/PNI DecryptionError unlink', function needsName() { await bootstrap.linkAndClose(); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { if (app) { await bootstrap.maybeSaveLogs(this.currentTest, app); await app.close(); diff --git a/ts/test-mock/pnp/send_gv2_invite_test.ts b/ts/test-mock/pnp/send_gv2_invite_test.ts index 7925a2adb7..01cca4a5fe 100644 --- a/ts/test-mock/pnp/send_gv2_invite_test.ts +++ b/ts/test-mock/pnp/send_gv2_invite_test.ts @@ -16,7 +16,7 @@ const IdentifierType = Proto.ManifestRecord.Identifier.Type; export const debug = createDebug('mock:test:gv2'); -describe('pnp/send gv2 invite', function needsName() { +describe('pnp/send gv2 invite', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -85,7 +85,7 @@ describe('pnp/send gv2 invite', function needsName() { app = await bootstrap.link(); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { await bootstrap.maybeSaveLogs(this.currentTest, app); await app.close(); await bootstrap.teardown(); diff --git a/ts/test-mock/pnp/username_test.ts b/ts/test-mock/pnp/username_test.ts index 31031efe72..e9707c105d 100644 --- a/ts/test-mock/pnp/username_test.ts +++ b/ts/test-mock/pnp/username_test.ts @@ -23,7 +23,7 @@ const USERNAME = 'signalapp.55'; const NICKNAME = 'signalapp'; const CARL_USERNAME = 'carl.84'; -describe('pnp/username', function needsName() { +describe('pnp/username', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -75,7 +75,7 @@ describe('pnp/username', function needsName() { app = await bootstrap.link(); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { await bootstrap.maybeSaveLogs(this.currentTest, app); await app.close(); await bootstrap.teardown(); diff --git a/ts/test-mock/rate-limit/story_test.ts b/ts/test-mock/rate-limit/story_test.ts index e00a31c288..456da0f12f 100644 --- a/ts/test-mock/rate-limit/story_test.ts +++ b/ts/test-mock/rate-limit/story_test.ts @@ -15,7 +15,7 @@ export const debug = createDebug('mock:test:rate-limit'); const IdentifierType = Proto.ManifestRecord.Identifier.Type; -describe('story/no-sender-key', function needsName() { +describe('story/no-sender-key', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -57,7 +57,7 @@ describe('story/no-sender-key', function needsName() { app = await bootstrap.link(); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { await bootstrap.maybeSaveLogs(this.currentTest, app); await app.close(); await bootstrap.teardown(); diff --git a/ts/test-mock/rate-limit/viewed_test.ts b/ts/test-mock/rate-limit/viewed_test.ts index f41efe0ec2..4db8b0d562 100644 --- a/ts/test-mock/rate-limit/viewed_test.ts +++ b/ts/test-mock/rate-limit/viewed_test.ts @@ -13,7 +13,7 @@ import { toUntaggedPni } from '../../types/ServiceId'; export const debug = createDebug('mock:test:challenge:receipts'); -describe('challenge/receipts', function challengeReceiptsTest() { +describe('challenge/receipts', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -65,7 +65,7 @@ describe('challenge/receipts', function challengeReceiptsTest() { await phone.setStorageState(state); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { await bootstrap.maybeSaveLogs(this.currentTest, app); await app.close(); await bootstrap.teardown(); diff --git a/ts/test-mock/storage/archive_test.ts b/ts/test-mock/storage/archive_test.ts index 7947eeb081..f2e0631c3a 100644 --- a/ts/test-mock/storage/archive_test.ts +++ b/ts/test-mock/storage/archive_test.ts @@ -7,7 +7,7 @@ import * as durations from '../../util/durations'; import type { App, Bootstrap } from './fixtures'; import { initStorage, debug } from './fixtures'; -describe('storage service', function needsName() { +describe('storage service', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -18,7 +18,7 @@ describe('storage service', function needsName() { ({ bootstrap, app } = await initStorage()); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { if (!bootstrap) { return; } diff --git a/ts/test-mock/storage/drop_test.ts b/ts/test-mock/storage/drop_test.ts index fc98f7deeb..89836791d7 100644 --- a/ts/test-mock/storage/drop_test.ts +++ b/ts/test-mock/storage/drop_test.ts @@ -10,7 +10,7 @@ import { initStorage, debug } from './fixtures'; const IdentifierType = Proto.ManifestRecord.Identifier.Type; -describe('storage service', function needsName() { +describe('storage service', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -21,7 +21,7 @@ describe('storage service', function needsName() { ({ bootstrap, app } = await initStorage()); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { if (!bootstrap) { return; } diff --git a/ts/test-mock/storage/max_read_keys_test.ts b/ts/test-mock/storage/max_read_keys_test.ts index 8216570d0e..a6dbe67bf2 100644 --- a/ts/test-mock/storage/max_read_keys_test.ts +++ b/ts/test-mock/storage/max_read_keys_test.ts @@ -12,7 +12,7 @@ import { initStorage, debug } from './fixtures'; const IdentifierType = Proto.ManifestRecord.Identifier.Type; -describe('storage service', function needsName() { +describe('storage service', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -23,7 +23,7 @@ describe('storage service', function needsName() { ({ bootstrap, app } = await initStorage()); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { if (!bootstrap) { return; } diff --git a/ts/test-mock/storage/message_request_test.ts b/ts/test-mock/storage/message_request_test.ts index af31ff4e51..3af91267f4 100644 --- a/ts/test-mock/storage/message_request_test.ts +++ b/ts/test-mock/storage/message_request_test.ts @@ -7,7 +7,7 @@ import * as durations from '../../util/durations'; import type { App, Bootstrap } from './fixtures'; import { initStorage, debug } from './fixtures'; -describe('storage service', function needsName() { +describe('storage service', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -18,7 +18,7 @@ describe('storage service', function needsName() { ({ bootstrap, app } = await initStorage()); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { if (!bootstrap) { return; } diff --git a/ts/test-mock/storage/pin_unpin_test.ts b/ts/test-mock/storage/pin_unpin_test.ts index dda379f618..02af2c441c 100644 --- a/ts/test-mock/storage/pin_unpin_test.ts +++ b/ts/test-mock/storage/pin_unpin_test.ts @@ -10,7 +10,7 @@ import * as durations from '../../util/durations'; import type { App, Bootstrap } from './fixtures'; import { initStorage, debug } from './fixtures'; -describe('storage service', function needsName() { +describe('storage service', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -22,7 +22,7 @@ describe('storage service', function needsName() { ({ bootstrap, app, group } = await initStorage()); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { if (!bootstrap) { return; } diff --git a/ts/test-mock/storage/sticker_test.ts b/ts/test-mock/storage/sticker_test.ts index 75656f2886..34f76a104d 100644 --- a/ts/test-mock/storage/sticker_test.ts +++ b/ts/test-mock/storage/sticker_test.ts @@ -62,7 +62,7 @@ function getStickerPackRecordPredicate( }; } -describe('storage service', function needsName() { +describe('storage service', function (this: Mocha.Suite) { this.timeout(durations.MINUTE); this.retries(4); @@ -95,7 +95,7 @@ describe('storage service', function needsName() { ); }); - afterEach(async function after() { + afterEach(async function (this: Mocha.Context) { if (!bootstrap) { return; } diff --git a/ts/test-node/app/SystemTrayService_test.ts b/ts/test-node/app/SystemTrayService_test.ts index 635af93f1e..c2ced014ee 100644 --- a/ts/test-node/app/SystemTrayService_test.ts +++ b/ts/test-node/app/SystemTrayService_test.ts @@ -16,7 +16,7 @@ import enMessages from '../../../_locales/en/messages.json'; const i18n = setupI18n('en', enMessages); -describe('SystemTrayService', function thisNeeded() { +describe('SystemTrayService', function (this: Mocha.Suite) { // These tests take more time on CI in some cases, so we increase the timeout. this.timeout(MINUTE); diff --git a/ts/test-node/app/base_config_test.ts b/ts/test-node/app/base_config_test.ts index 6d4d15958f..43a755f973 100644 --- a/ts/test-node/app/base_config_test.ts +++ b/ts/test-node/app/base_config_test.ts @@ -83,7 +83,7 @@ describe('base_config', () => { assert.deepEqual(_getCachedValue(), Object.create(null)); }); - it('handles a file that cannot be opened, if told to', function test() { + it('handles a file that cannot be opened, if told to', function (this: Mocha.Context) { if (process.platform === 'win32') { this.skip(); } @@ -227,7 +227,7 @@ describe('base_config', () => { }); describe('throwOnFilesystemErrors: true', () => { - it("doesn't update the local cache if file removal fails", async function test() { + it("doesn't update the local cache if file removal fails", async function (this: Mocha.Context) { if (process.platform === 'win32') { this.skip(); } @@ -252,7 +252,7 @@ describe('base_config', () => { }); describe('throwOnFilesystemErrors: false', () => { - it('updates the local cache even if file removal fails', async function test() { + it('updates the local cache even if file removal fails', async function (this: Mocha.Context) { if (process.platform === 'win32') { this.skip(); } diff --git a/ts/test-node/logging/uploadDebugLogs_test.ts b/ts/test-node/logging/uploadDebugLogs_test.ts index bd6bc1234a..f624e3653e 100644 --- a/ts/test-node/logging/uploadDebugLogs_test.ts +++ b/ts/test-node/logging/uploadDebugLogs_test.ts @@ -15,7 +15,7 @@ import * as logger from '../../logging/log'; const gzip: (_: zlib.InputType) => Promise = util.promisify(zlib.gzip); describe('upload', () => { - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { this.sandbox = sinon.createSandbox(); this.sandbox.stub(process, 'platform').get(() => 'freebsd'); @@ -35,11 +35,11 @@ describe('upload', () => { this.fakePost.resolves({ statusCode: 204 }); }); - afterEach(function afterEach() { + afterEach(function (this: Mocha.Context) { this.sandbox.restore(); }); - it('makes a request to get the S3 bucket, then uploads it there', async function test() { + it('makes a request to get the S3 bucket, then uploads it there', async function (this: Mocha.Context) { assert.strictEqual( await upload({ content: 'hello world', appVersion: '1.2.3', logger }), 'https://debuglogs.org/abc123.gz' @@ -75,7 +75,7 @@ describe('upload', () => { }); }); - it("rejects if we can't get a token", async function test() { + it("rejects if we can't get a token", async function (this: Mocha.Context) { this.fakeGet.rejects(new Error('HTTP request failure')); let err: unknown; @@ -87,7 +87,7 @@ describe('upload', () => { assert.instanceOf(err, Error); }); - it('rejects with an invalid token body', async function test() { + it('rejects with an invalid token body', async function (this: Mocha.Context) { const bodies = [ null, {}, @@ -114,7 +114,7 @@ describe('upload', () => { } }); - it("rejects if the upload doesn't return a 204", async function test() { + it("rejects if the upload doesn't return a 204", async function (this: Mocha.Context) { this.fakePost.resolves({ statusCode: 400 }); let err: unknown; diff --git a/ts/test-node/util/getUserAgent_test.ts b/ts/test-node/util/getUserAgent_test.ts index 4761d1ae69..5eedc7a446 100644 --- a/ts/test-node/util/getUserAgent_test.ts +++ b/ts/test-node/util/getUserAgent_test.ts @@ -7,15 +7,15 @@ import * as sinon from 'sinon'; import { getUserAgent } from '../../util/getUserAgent'; describe('getUserAgent', () => { - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { this.sandbox = sinon.createSandbox(); }); - afterEach(function afterEach() { + afterEach(function (this: Mocha.Context) { this.sandbox.restore(); }); - it('returns the right User-Agent on Windows', function test() { + it('returns the right User-Agent on Windows', function (this: Mocha.Context) { this.sandbox.stub(process, 'platform').get(() => 'win32'); assert.strictEqual( getUserAgent('1.2.3', '10.0.22000'), @@ -23,7 +23,7 @@ describe('getUserAgent', () => { ); }); - it('returns the right User-Agent on macOS', function test() { + it('returns the right User-Agent on macOS', function (this: Mocha.Context) { this.sandbox.stub(process, 'platform').get(() => 'darwin'); assert.strictEqual( getUserAgent('1.2.3', '21.5.0'), @@ -31,7 +31,7 @@ describe('getUserAgent', () => { ); }); - it('returns the right User-Agent on Linux', function test() { + it('returns the right User-Agent on Linux', function (this: Mocha.Context) { this.sandbox.stub(process, 'platform').get(() => 'linux'); assert.strictEqual( getUserAgent('1.2.3', '20.04'), @@ -39,7 +39,7 @@ describe('getUserAgent', () => { ); }); - it('omits the platform on unsupported platforms', function test() { + it('omits the platform on unsupported platforms', function (this: Mocha.Context) { this.sandbox.stub(process, 'platform').get(() => 'freebsd'); assert.strictEqual(getUserAgent('1.2.3', '13.1'), 'Signal-Desktop/1.2.3'); }); diff --git a/ts/test-node/util/sleep_test.ts b/ts/test-node/util/sleep_test.ts index 5ee1660fbf..6165d32b2e 100644 --- a/ts/test-node/util/sleep_test.ts +++ b/ts/test-node/util/sleep_test.ts @@ -7,17 +7,16 @@ import { useFakeTimers } from 'sinon'; import { sleep } from '../../util/sleep'; describe('sleep', () => { - beforeEach(function beforeEach() { + beforeEach(function (this: Mocha.Context) { // This isn't a hook. - // eslint-disable-next-line react-hooks/rules-of-hooks this.clock = useFakeTimers(); }); - afterEach(function afterEach() { + afterEach(function (this: Mocha.Context) { this.clock.restore(); }); - it('returns a promise that resolves after the specified number of milliseconds', async function test() { + it('returns a promise that resolves after the specified number of milliseconds', async function (this: Mocha.Context) { let isDone = false; void (async () => { diff --git a/ts/test-node/util/windowsZoneIdentifier_test.ts b/ts/test-node/util/windowsZoneIdentifier_test.ts index fdab807182..74f70bcffb 100644 --- a/ts/test-node/util/windowsZoneIdentifier_test.ts +++ b/ts/test-node/util/windowsZoneIdentifier_test.ts @@ -11,25 +11,25 @@ import * as Sinon from 'sinon'; import { writeWindowsZoneIdentifier } from '../../util/windowsZoneIdentifier'; describe('writeWindowsZoneIdentifier', () => { - before(function thisNeeded() { + before(function (this: Mocha.Context) { if (process.platform !== 'win32') { this.skip(); } }); - beforeEach(async function thisNeeded() { + beforeEach(async function (this: Mocha.Context) { this.sandbox = Sinon.createSandbox(); this.tmpdir = await fs.promises.mkdtemp( path.join(os.tmpdir(), 'signal-test-') ); }); - afterEach(async function thisNeeded() { + afterEach(async function (this: Mocha.Context) { this.sandbox.restore(); await fse.remove(this.tmpdir); }); - it('writes zone transfer ID 3 (internet) to the Zone.Identifier file', async function thisNeeded() { + it('writes zone transfer ID 3 (internet) to the Zone.Identifier file', async function (this: Mocha.Context) { const file = path.join(this.tmpdir, 'file.txt'); await fse.outputFile(file, 'hello'); @@ -41,7 +41,7 @@ describe('writeWindowsZoneIdentifier', () => { ); }); - it('fails if there is an existing Zone.Identifier file', async function thisNeeded() { + it('fails if there is an existing Zone.Identifier file', async function (this: Mocha.Context) { const file = path.join(this.tmpdir, 'file.txt'); await fse.outputFile(file, 'hello'); await fs.promises.writeFile(`${file}:Zone.Identifier`, '# already here'); @@ -49,13 +49,13 @@ describe('writeWindowsZoneIdentifier', () => { await assert.isRejected(writeWindowsZoneIdentifier(file)); }); - it('fails if the original file does not exist', async function thisNeeded() { + it('fails if the original file does not exist', async function (this: Mocha.Context) { const file = path.join(this.tmpdir, 'file-never-created.txt'); await assert.isRejected(writeWindowsZoneIdentifier(file)); }); - it('fails if not on Windows', async function thisNeeded() { + it('fails if not on Windows', async function (this: Mocha.Context) { this.sandbox.stub(process, 'platform').get(() => 'darwin'); const file = path.join(this.tmpdir, 'file.txt'); diff --git a/ts/textsecure/cds/CDSSocketBase.ts b/ts/textsecure/cds/CDSSocketBase.ts index ccb730af1d..69f4160d91 100644 --- a/ts/textsecure/cds/CDSSocketBase.ts +++ b/ts/textsecure/cds/CDSSocketBase.ts @@ -12,7 +12,7 @@ import { strictAssert } from '../../util/assert'; import { isUntaggedPniString, toTaggedPni } from '../../types/ServiceId'; import { isAciString } from '../../util/isAciString'; import * as Bytes from '../../Bytes'; -import { UUID_BYTE_SIZE } from '../../Crypto'; +import { UUID_BYTE_SIZE } from '../../types/Crypto'; import { uuidToBytes, bytesToUuid } from '../../util/uuidToBytes'; import { SignalService as Proto } from '../../protobuf'; import type { diff --git a/ts/types/Crypto.ts b/ts/types/Crypto.ts index 5bd856c3b0..c3b847b7c9 100644 --- a/ts/types/Crypto.ts +++ b/ts/types/Crypto.ts @@ -11,3 +11,5 @@ export enum CipherType { AES256CTR = 'aes-256-ctr', AES256GCM = 'aes-256-gcm', } + +export const UUID_BYTE_SIZE = 16; diff --git a/ts/types/ForwardDraft.ts b/ts/types/ForwardDraft.ts new file mode 100644 index 0000000000..91f98520be --- /dev/null +++ b/ts/types/ForwardDraft.ts @@ -0,0 +1,81 @@ +// Copyright 2023 Signal Messenger, LLC +// SPDX-License-Identifier: AGPL-3.0-only + +import { orderBy } from 'lodash'; +import type { MessageAttributesType } from '../model-types'; +import { + isVoiceMessage, + type AttachmentType, + isDownloaded, +} from './Attachment'; +import type { HydratedBodyRangesType } from './BodyRange'; +import type { LinkPreviewType } from './message/LinkPreviews'; + +export type MessageForwardDraft = Readonly<{ + attachments?: ReadonlyArray; + bodyRanges?: HydratedBodyRangesType; + hasContact: boolean; + isSticker: boolean; + messageBody?: string; + originalMessageId: string; + previews: ReadonlyArray; +}>; + +export type ForwardMessageData = Readonly<{ + originalMessage: MessageAttributesType; + draft: MessageForwardDraft; +}>; + +export function isDraftEditable(draft: MessageForwardDraft): boolean { + if (draft.isSticker) { + return false; + } + if (draft.hasContact) { + return false; + } + const hasVoiceMessage = draft.attachments?.some(isVoiceMessage) ?? false; + if (hasVoiceMessage) { + return false; + } + return true; +} + +function isDraftEmpty(draft: MessageForwardDraft) { + const { messageBody, attachments, isSticker, hasContact } = draft; + if (isSticker || hasContact) { + return false; + } + if (attachments != null && attachments.length > 0) { + return false; + } + if (messageBody != null && messageBody.length > 0) { + return false; + } + return true; +} + +export function isDraftForwardable(draft: MessageForwardDraft): boolean { + const { attachments } = draft; + if (isDraftEmpty(draft)) { + return false; + } + if (attachments != null && attachments.length > 0) { + if (!attachments.every(isDownloaded)) { + return false; + } + } + return true; +} + +export function sortByMessageOrder( + items: ReadonlyArray, + getMesssage: ( + item: T + ) => Pick +): Array { + return orderBy( + items, + [item => getMesssage(item).received_at, item => getMesssage(item).sent_at], + ['ASC', 'ASC'] + ); +} diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 5b688a83e4..8f81b2df3f 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -15,25 +15,6 @@ "updated": "2018-09-18T19:19:27.699Z", "reasonDetail": "Part of runtime library for C++ transpiled code" }, - { - "rule": "React-useRef", - "path": "node_modules/@design-systems/utils/dist/cjs/utils/focus-lock.js", - "reasonCategory": "testCode", - "updated": "2022-06-23T23:21:04.555Z" - }, - { - "rule": "React-useRef", - "path": "node_modules/@design-systems/utils/dist/esm/utils/focus-lock.js", - "reasonCategory": "testCode", - "updated": "2022-06-23T23:21:04.555Z" - }, - { - "rule": "React-useRef", - "path": "node_modules/@design-systems/utils/src/utils/focus-lock.tsx", - "line": " const trap = React.useRef(null);", - "reasonCategory": "testCode", - "updated": "2022-06-23T23:21:04.555Z" - }, { "rule": "React-createRef", "path": "node_modules/@indutny/frameless-titlebar/dist/index.es.js", @@ -188,6 +169,13 @@ "reasonCategory": "usageTrusted", "updated": "2022-06-06T22:58:37.359Z" }, + { + "rule": "DOM-innerHTML", + "path": "node_modules/@ndelangen/get-tarball/dist/index.js", + "line": " \"innerHTML\",", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, { "rule": "eval", "path": "node_modules/@protobufjs/inquire/index.js", @@ -196,6 +184,188 @@ "updated": "2018-09-18T19:19:27.699Z", "reasonDetail": "What's being eval'd is a static string" }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-collection/dist/index.js", + "line": " const ref = ($parcel$interopDefault($hnlpS$react)).useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-collection/dist/index.js", + "line": " const itemMap = ($parcel$interopDefault($hnlpS$react)).useRef(new Map()).current;", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-collection/dist/index.js", + "line": " const ref = ($parcel$interopDefault($hnlpS$react)).useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-dismissable-layer/dist/index.js", + "line": " const ref = $g2vWm$react.useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-dismissable-layer/dist/index.js", + "line": " const isPointerInsideReactTreeRef = $g2vWm$react.useRef(false);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-dismissable-layer/dist/index.js", + "line": " const handleClickRef = $g2vWm$react.useRef(()=>{});", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-dismissable-layer/dist/index.js", + "line": " const isFocusInsideReactTreeRef = $g2vWm$react.useRef(false);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-focus-scope/dist/index.js", + "line": " const lastFocusedElementRef = $buum9$react.useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-focus-scope/dist/index.js", + "line": " const focusScope = $buum9$react.useRef({", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-popper/dist/index.js", + "line": " const ref = $50Iv9$react.useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-roving-focus/dist/index.js", + "line": " const ref = $9QJ9Y$react.useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-roving-focus/dist/index.js", + "line": " const isClickFocusRef = $9QJ9Y$react.useRef(false);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-dangerouslySetInnerHTML", + "path": "node_modules/@radix-ui/react-select/dist/index.js", + "line": " dangerouslySetInnerHTML: {", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-select/dist/index.js", + "line": " const triggerPointerDownPosRef = $cg2C9$react.useRef(null); // We set this to true by default so that events bubble to forms without JS (SSR)", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-select/dist/index.js", + "line": " const firstValidItemFoundRef = $cg2C9$react.useRef(false); // aria-hide everything except the content (better supported equivalent to setting aria-modal)", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-select/dist/index.js", + "line": " const shouldExpandOnScrollRef = $cg2C9$react.useRef(false);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-select/dist/index.js", + "line": " const shouldRepositionRef = $cg2C9$react.useRef(true);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-select/dist/index.js", + "line": " const prevScrollTopRef = $cg2C9$react.useRef(0);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-select/dist/index.js", + "line": " const autoScrollTimerRef = $cg2C9$react.useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-select/dist/index.js", + "line": " const ref = $cg2C9$react.useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-select/dist/index.js", + "line": " const searchRef = $cg2C9$react.useRef('');", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-select/dist/index.js", + "line": " const timerRef = $cg2C9$react.useRef(0);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-toggle-group/dist/index.js", + "line": " const ref = ($parcel$interopDefault($1z6X1$react)).useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-use-callback-ref/dist/index.js", + "line": " const callbackRef = $92muK$react.useRef(callback);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-use-controllable-state/dist/index.js", + "line": " const prevValueRef = $ijazI$react.useRef(value);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@radix-ui/react-use-previous/dist/index.js", + "line": " const ref = $kjM8v$react.useRef({", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, { "rule": "React-useRef", "path": "node_modules/@react-spring/animated/dist/react-spring-animated.cjs.dev.js", @@ -434,13 +604,6 @@ "reasonCategory": "falseMatch", "updated": "2022-06-23T23:21:04.555Z" }, - { - "rule": "DOM-innerHTML", - "path": "node_modules/@testing-library/dom/dist/@testing-library/dom.cjs.js", - "line": " console.log(\"Open this URL in your browser\\n\\n\" + getPlaygroundUrl(element.innerHTML));", - "reasonCategory": "testCode", - "updated": "2022-06-23T23:21:04.555Z" - }, { "rule": "DOM-outerHTML", "path": "node_modules/@testing-library/dom/dist/@testing-library/dom.cjs.js", @@ -469,13 +632,6 @@ "reasonCategory": "falseMatch", "updated": "2022-06-23T23:21:04.555Z" }, - { - "rule": "DOM-innerHTML", - "path": "node_modules/@testing-library/dom/dist/@testing-library/dom.esm.js", - "line": " console.log(\"Open this URL in your browser\\n\\n\" + getPlaygroundUrl(element.innerHTML));", - "reasonCategory": "usageTrusted", - "updated": "2022-06-23T23:21:04.555Z" - }, { "rule": "DOM-outerHTML", "path": "node_modules/@testing-library/dom/dist/@testing-library/dom.esm.js", @@ -504,13 +660,6 @@ "reasonCategory": "falseMatch", "updated": "2022-06-23T23:21:04.555Z" }, - { - "rule": "DOM-innerHTML", - "path": "node_modules/@testing-library/dom/dist/@testing-library/dom.umd.js", - "line": " console.log(\"Open this URL in your browser\\n\\n\" + getPlaygroundUrl(element.innerHTML));", - "reasonCategory": "testCode", - "updated": "2022-06-23T23:21:04.555Z" - }, { "rule": "DOM-outerHTML", "path": "node_modules/@testing-library/dom/dist/@testing-library/dom.umd.js", @@ -565,61 +714,89 @@ "reasonCategory": "testCode", "updated": "2022-06-23T23:21:04.555Z" }, + { + "rule": "DOM-innerHTML", + "path": "node_modules/@testing-library/dom/dist/@testing-library/dom.cjs.js", + "line": " const playgroundUrl = getPlaygroundUrl(element.innerHTML);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "DOM-innerHTML", + "path": "node_modules/@testing-library/dom/dist/@testing-library/dom.esm.js", + "line": " const playgroundUrl = getPlaygroundUrl(element.innerHTML);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "DOM-innerHTML", + "path": "node_modules/@testing-library/dom/dist/@testing-library/dom.umd.js", + "line": "\t const playgroundUrl = getPlaygroundUrl(element.innerHTML);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, { "rule": "DOM-innerHTML", "path": "node_modules/@testing-library/dom/dist/screen.js", - "line": " console.log(`Open this URL in your browser\\n\\n${getPlaygroundUrl(element.innerHTML)}`);", - "reasonCategory": "testCode", - "updated": "2022-06-23T23:21:04.555Z" + "line": " const playgroundUrl = getPlaygroundUrl(element.innerHTML);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" }, { "rule": "DOM-innerHTML", - "path": "node_modules/@testing-library/jest-dom/dist/to-be-empty-dom-element.js", - "line": " return [this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toBeEmptyDOMElement`, 'element', ''), '', 'Received:', ` ${this.utils.printReceived(element.innerHTML)}`].join('\\n');", - "reasonCategory": "testCode", - "updated": "2022-06-23T23:21:04.555Z" - }, - { - "rule": "DOM-innerHTML", - "path": "node_modules/@testing-library/jest-dom/dist/to-be-empty.js", + "path": "node_modules/@testing-library/jest-dom/dist/matchers-d012a0f1.js", "line": " pass: element.innerHTML === '',", - "reasonCategory": "testCode", - "updated": "2022-06-23T23:21:04.555Z" + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" }, { "rule": "DOM-innerHTML", - "path": "node_modules/@testing-library/jest-dom/dist/to-be-empty.js", - "line": " return [this.utils.matcherHint(`${this.isNot ? '.not' : ''}.toBeEmpty`, 'element', ''), '', 'Received:', ` ${this.utils.printReceived(element.innerHTML)}`].join('\\n');", - "reasonCategory": "testCode", - "updated": "2022-06-23T23:21:04.555Z" + "path": "node_modules/@testing-library/jest-dom/dist/matchers-d012a0f1.js", + "line": " ` ${this.utils.printReceived(element.innerHTML)}`,", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" }, { "rule": "DOM-innerHTML", - "path": "node_modules/@testing-library/jest-dom/dist/to-contain-html.js", + "path": "node_modules/@testing-library/jest-dom/dist/matchers-d012a0f1.js", + "line": " ` ${this.utils.printReceived(element.innerHTML)}`,", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "DOM-innerHTML", + "path": "node_modules/@testing-library/jest-dom/dist/matchers-d012a0f1.js", "line": " div.innerHTML = htmlText;", - "reasonCategory": "testCode", - "updated": "2022-06-23T23:21:04.555Z" + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" }, { "rule": "DOM-innerHTML", - "path": "node_modules/@testing-library/jest-dom/dist/to-contain-html.js", - "line": " return div.innerHTML;", - "reasonCategory": "testCode", - "updated": "2022-06-23T23:21:04.555Z" + "path": "node_modules/@testing-library/jest-dom/dist/matchers-d012a0f1.js", + "line": " return div.innerHTML", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" }, { "rule": "DOM-outerHTML", - "path": "node_modules/@testing-library/jest-dom/dist/to-contain-html.js", + "path": "node_modules/@testing-library/jest-dom/dist/matchers-d012a0f1.js", "line": " pass: container.outerHTML.includes(getNormalizedHtml(container, htmlText)),", - "reasonCategory": "testCode", - "updated": "2022-06-23T23:21:04.555Z" + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" }, { "rule": "DOM-innerHTML", - "path": "node_modules/@testing-library/user-event/dist/select-options.js", - "line": " const matchingOption = allOptions.find(o => o.value === val || o.innerHTML === val);", - "reasonCategory": "testCode", - "updated": "2022-06-23T23:21:04.555Z" + "path": "node_modules/@testing-library/user-event/dist/cjs/utility/selectOptions.js", + "line": " const matchingOption = allOptions.find((o)=>o.value === val || o.innerHTML === val);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "DOM-innerHTML", + "path": "node_modules/@testing-library/user-event/dist/esm/utility/selectOptions.js", + "line": " const matchingOption = allOptions.find((o)=>o.value === val || o.innerHTML === val);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" }, { "rule": "eval", @@ -696,13 +873,6 @@ "reasonCategory": "falseMatch", "updated": "2018-09-15T00:38:04.183Z" }, - { - "rule": "thenify-multiArgs", - "path": "node_modules/default-browser-id/node_modules/pify/index.js", - "line": "\t\t\t\t} else if (opts.multiArgs) {", - "reasonCategory": "usageTrusted", - "updated": "2023-04-20T16:43:40.643Z" - }, { "rule": "DOM-outerHTML", "path": "node_modules/domutils/node_modules/dom-serializer/lib/esm/index.js", @@ -717,40 +887,6 @@ "reasonCategory": "falseMatch", "updated": "2023-01-11T23:51:00.603Z" }, - { - "rule": "DOM-innerHTML", - "path": "node_modules/element-resize-detector/dist/element-resize-detector.js", - "line": " div.innerHTML = \"\";", - "reasonCategory": "usageTrusted", - "updated": "2022-06-01T22:57:44.591Z" - }, - { - "rule": "DOM-innerHTML", - "path": "node_modules/element-resize-detector/dist/element-resize-detector.js", - "line": " styleElement.innerHTML = style;", - "reasonCategory": "usageTrusted", - "updated": "2022-06-01T22:57:44.591Z" - }, - { - "rule": "DOM-innerHTML", - "path": "node_modules/element-resize-detector/dist/element-resize-detector.min.js", - "reasonCategory": "usageTrusted", - "updated": "2022-06-01T22:57:44.591Z" - }, - { - "rule": "DOM-innerHTML", - "path": "node_modules/element-resize-detector/src/browser-detector.js", - "line": " div.innerHTML = \"\";", - "reasonCategory": "usageTrusted", - "updated": "2022-06-01T22:57:44.591Z" - }, - { - "rule": "DOM-innerHTML", - "path": "node_modules/element-resize-detector/src/detection-strategy/scroll.js", - "line": " styleElement.innerHTML = style;", - "reasonCategory": "usageTrusted", - "updated": "2022-06-01T22:57:44.591Z" - }, { "rule": "React-ref", "path": "node_modules/esquery/dist/esquery.esm.min.js", @@ -765,6 +901,95 @@ "updated": "2020-08-26T00:10:28.628Z", "reasonDetail": "isn't react" }, + { + "rule": "eval", + "path": "node_modules/expect-playwright/lib/matchers/toHaveSelectorCount/index.js", + "line": " const actualCount = await elementHandle.$$eval(selector, (el) => el.length);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.esm.js", + "line": " const ref = React.useRef(value);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.esm.js", + "line": " const referenceRef = React.useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.esm.js", + "line": " const floatingRef = React.useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.esm.js", + "line": " const dataRef = React.useRef(data);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.esm.js", + "line": " const isMountedRef = React.useRef(false);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.esm.min.js", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.umd.js", + "line": " const ref = React__namespace.useRef(value);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.umd.js", + "line": " const referenceRef = React__namespace.useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.umd.js", + "line": " const floatingRef = React__namespace.useRef(null);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.umd.js", + "line": " const dataRef = React__namespace.useRef(data);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.umd.js", + "line": " const isMountedRef = React__namespace.useRef(false);", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/@floating-ui/react-dom/dist/floating-ui.react-dom.umd.min.js", + "reasonCategory": "usageTrusted", + "updated": "2023-10-03T18:55:06.301Z" + }, { "rule": "React-findDOMNode", "path": "node_modules/focus-trap-react/dist/focus-trap-react.js", @@ -852,6 +1077,190 @@ "updated": "2022-07-26T23:41:36.800Z", "reasonDetail": "Part of keyword list for preservation in final minified build" }, + { + "rule": "DOM-innerHTML", + "path": "node_modules/tocbot/dist/tocbot.js", + "line": " tocElement.innerHTML = ''", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "DOM-innerHTML", + "path": "node_modules/tocbot/dist/tocbot.min.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "DOM-innerHTML", + "path": "node_modules/tocbot/out/_next/static/chunks/253-af2be75f8688092d.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-dangerouslySetInnerHTML", + "path": "node_modules/tocbot/out/_next/static/chunks/253-af2be75f8688092d.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-ref", + "path": "node_modules/tocbot/out/_next/static/chunks/684-c4d85164cfbebace.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/tocbot/out/_next/static/chunks/684-c4d85164cfbebace.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-dangerouslySetInnerHTML", + "path": "node_modules/tocbot/out/_next/static/chunks/894.d8d3be35b7c84b33.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-ref", + "path": "node_modules/tocbot/out/_next/static/chunks/894.d8d3be35b7c84b33.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "DOM-innerHTML", + "path": "node_modules/tocbot/out/_next/static/chunks/framework-3911a61406e859ea.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-dangerouslySetInnerHTML", + "path": "node_modules/tocbot/out/_next/static/chunks/framework-3911a61406e859ea.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-ref", + "path": "node_modules/tocbot/out/_next/static/chunks/framework-3911a61406e859ea.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/tocbot/out/_next/static/chunks/framework-3911a61406e859ea.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "DOM-innerHTML", + "path": "node_modules/tocbot/out/_next/static/chunks/main-292648d00afc3512.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-dangerouslySetInnerHTML", + "path": "node_modules/tocbot/out/_next/static/chunks/main-292648d00afc3512.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-ref", + "path": "node_modules/tocbot/out/_next/static/chunks/main-292648d00afc3512.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-useRef", + "path": "node_modules/tocbot/out/_next/static/chunks/main-292648d00afc3512.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-dangerouslySetInnerHTML", + "path": "node_modules/tocbot/out/_next/static/chunks/pages/_error-b0eae765db80170a.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-ref", + "path": "node_modules/tocbot/out/_next/static/chunks/pages/_error-b0eae765db80170a.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "DOM-innerHTML", + "path": "node_modules/tocbot/out/static/js/tocbot.js", + "line": " tocElement.innerHTML = ''", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "DOM-innerHTML", + "path": "node_modules/tocbot/out/static/js/tocbot.min.js", + "reasonCategory": "notExercisedByOurApp", + "updated": "2023-10-03T18:55:06.301Z" + }, + { + "rule": "React-dangerouslySetInnerHTML", + "path": "node_modules/tocbot/src/components/Template/Tracking/index.js", + "line": "