diff --git a/.eslintrc.js b/.eslintrc.js index 5c23c7be0839..6194ccd39d3f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -24,6 +24,26 @@ const restrictedImportPaths = [ importNames: ['CSSProperties'], message: "Please use 'ViewStyle', 'TextStyle', 'ImageStyle' from 'react-native' instead.", }, + { + name: '@styles/index', + importNames: ['default', 'defaultStyles'], + message: 'Do not import styles directly. Please use the `useThemeStyles` hook or `withThemeStyles` HOC instead.', + }, + { + name: '@styles/utils', + importNames: ['default', 'DefaultStyleUtils'], + message: 'Do not import StyleUtils directly. Please use the `useStyleUtils` hook or `withStyleUtils` HOC instead.', + }, + { + name: '@styles/theme', + importNames: ['default', 'defaultTheme'], + + message: 'Do not import themes directly. Please use the `useTheme` hook or `withTheme` HOC instead.', + }, + { + name: '@styles/theme/illustrations', + message: 'Do not import theme illustrations directly. Please use the `useThemeIllustrations` hook instead.', + }, ]; const restrictedImportPatterns = [ @@ -31,6 +51,18 @@ const restrictedImportPatterns = [ group: ['**/assets/animations/**/*.json'], message: "Do not import animations directly. Please use the 'src/components/LottieAnimations' import instead.", }, + { + group: ['@styles/theme/themes/**'], + message: 'Do not import themes directly. Please use the `useTheme` hook or `withTheme` HOC instead.', + }, + { + group: ['@styles/utils/**', '!@styles/utils/FontUtils', '!@styles/utils/types'], + message: 'Do not import style util functions directly. Please use the `useStyleUtils` hook or `withStyleUtils` HOC instead.', + }, + { + group: ['@styles/theme/illustrations/themes/**'], + message: 'Do not import theme illustrations directly. Please use the `useThemeIllustrations` hook instead.', + }, ]; module.exports = { @@ -171,6 +203,21 @@ module.exports = { '@typescript-eslint/switch-exhaustiveness-check': 'error', '@typescript-eslint/consistent-type-definitions': ['error', 'type'], '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/consistent-type-imports': [ + 'error', + { + prefer: 'type-imports', + fixStyle: 'separate-type-imports', + }, + ], + '@typescript-eslint/no-import-type-side-effects': 'error', + '@typescript-eslint/consistent-type-exports': [ + 'error', + { + fixMixedExportsWithInlineTypeSpecifier: false, + }, + ], + 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], 'es/no-nullish-coalescing-operators': 'off', 'es/no-optional-chaining': 'off', 'valid-jsdoc': 'off', diff --git a/.github/actions/javascript/authorChecklist/categories/index.ts b/.github/actions/javascript/authorChecklist/categories/index.ts index 53e0ed2ab838..5ed63c647e74 100644 --- a/.github/actions/javascript/authorChecklist/categories/index.ts +++ b/.github/actions/javascript/authorChecklist/categories/index.ts @@ -1,4 +1,4 @@ -import Category from './Category'; +import type Category from './Category'; import newComponent from './newComponentCategory'; const categories: Category[] = [newComponent]; diff --git a/.github/actions/javascript/authorChecklist/categories/newComponentCategory.ts b/.github/actions/javascript/authorChecklist/categories/newComponentCategory.ts index 63e26c015a5a..405a687b5d3e 100644 --- a/.github/actions/javascript/authorChecklist/categories/newComponentCategory.ts +++ b/.github/actions/javascript/authorChecklist/categories/newComponentCategory.ts @@ -4,7 +4,7 @@ import traverse from '@babel/traverse'; import CONST from '../../../../libs/CONST'; import GithubUtils from '../../../../libs/GithubUtils'; import promiseSome from '../../../../libs/promiseSome'; -import Category from './Category'; +import type Category from './Category'; type SuperClassType = {superClass: {name?: string; object: {name: string}; property: {name: string}} | null; name: string}; diff --git a/.github/actions/javascript/validateReassureOutput/action.yml b/.github/actions/javascript/validateReassureOutput/action.yml index 4fd53e838fb5..b3b18c244a8f 100644 --- a/.github/actions/javascript/validateReassureOutput/action.yml +++ b/.github/actions/javascript/validateReassureOutput/action.yml @@ -7,9 +7,6 @@ inputs: COUNT_DEVIATION: description: Allowable deviation for the mean count in regression test results. required: true - REGRESSION_OUTPUT: - description: Refers to the results obtained from regression tests `.reassure/output.json`. - required: true runs: using: 'node20' main: './index.js' diff --git a/.github/actions/javascript/validateReassureOutput/index.js b/.github/actions/javascript/validateReassureOutput/index.js index 6cc59af1de48..e70c379697cd 100644 --- a/.github/actions/javascript/validateReassureOutput/index.js +++ b/.github/actions/javascript/validateReassureOutput/index.js @@ -8,9 +8,10 @@ /***/ ((module, __unused_webpack_exports, __nccwpck_require__) => { const core = __nccwpck_require__(186); +const fs = __nccwpck_require__(147); const run = () => { - const regressionOutput = JSON.parse(core.getInput('REGRESSION_OUTPUT', {required: true})); + const regressionOutput = JSON.parse(fs.readFileSync('.reassure/output.json', 'utf8')); const countDeviation = core.getInput('COUNT_DEVIATION', {required: true}); const durationDeviation = core.getInput('DURATION_DEVIATION_PERCENTAGE', {required: true}); diff --git a/.github/actions/javascript/validateReassureOutput/validateReassureOutput.js b/.github/actions/javascript/validateReassureOutput/validateReassureOutput.js index da81d88c9885..214dc9e8b6d4 100644 --- a/.github/actions/javascript/validateReassureOutput/validateReassureOutput.js +++ b/.github/actions/javascript/validateReassureOutput/validateReassureOutput.js @@ -1,7 +1,8 @@ const core = require('@actions/core'); +const fs = require('fs'); const run = () => { - const regressionOutput = JSON.parse(core.getInput('REGRESSION_OUTPUT', {required: true})); + const regressionOutput = JSON.parse(fs.readFileSync('.reassure/output.json', 'utf8')); const countDeviation = core.getInput('COUNT_DEVIATION', {required: true}); const durationDeviation = core.getInput('DURATION_DEVIATION_PERCENTAGE', {required: true}); diff --git a/.github/workflows/platformDeploy.yml b/.github/workflows/platformDeploy.yml index 6aeecb3b4e05..813c341caaf6 100644 --- a/.github/workflows/platformDeploy.yml +++ b/.github/workflows/platformDeploy.yml @@ -10,7 +10,6 @@ on: env: SHOULD_DEPLOY_PRODUCTION: ${{ github.event_name == 'release' }} - DEVELOPER_DIR: /Applications/Xcode_15.0.1.app/Contents/Developer concurrency: group: ${{ github.workflow }}-${{ github.event_name }} @@ -174,6 +173,8 @@ jobs: name: Build and deploy iOS needs: validateActor if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} + env: + DEVELOPER_DIR: /Applications/Xcode_15.0.1.app/Contents/Developer runs-on: macos-13-xlarge steps: - name: Checkout diff --git a/.github/workflows/reassurePerformanceTests.yml b/.github/workflows/reassurePerformanceTests.yml index c49530c46faa..116f178868c1 100644 --- a/.github/workflows/reassurePerformanceTests.yml +++ b/.github/workflows/reassurePerformanceTests.yml @@ -36,17 +36,10 @@ jobs: npm install --force npx reassure --branch - - name: Read output.json - id: reassure - uses: juliangruber/read-file-action@v1 - with: - path: .reassure/output.json - - name: Validate output.json id: validateReassureOutput uses: ./.github/actions/javascript/validateReassureOutput with: DURATION_DEVIATION_PERCENTAGE: 20 COUNT_DEVIATION: 0 - REGRESSION_OUTPUT: ${{ steps.reassure.outputs.content }} diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index 8b18b8aa5d53..25a14fb27e1b 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -10,9 +10,6 @@ on: types: [opened, synchronize, labeled] branches: ['*ci-test/**'] -env: - DEVELOPER_DIR: /Applications/Xcode_15.0.1.app/Contents/Developer - jobs: validateActor: runs-on: ubuntu-latest @@ -139,6 +136,7 @@ jobs: if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} env: PULL_REQUEST_NUMBER: ${{ github.event.number || github.event.inputs.PULL_REQUEST_NUMBER }} + DEVELOPER_DIR: /Applications/Xcode_15.0.1.app/Contents/Developer runs-on: macos-13-xlarge steps: - name: Checkout diff --git a/android/app/build.gradle b/android/app/build.gradle index e93150b48fca..aaa6aaeb7a78 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -96,8 +96,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001042101 - versionName "1.4.21-1" + versionCode 1001042200 + versionName "1.4.22-0" } flavorDimensions "default" diff --git a/assets/emojis/common.ts b/assets/emojis/common.ts index cbefb21cf2d6..b23383590c51 100644 --- a/assets/emojis/common.ts +++ b/assets/emojis/common.ts @@ -7,7 +7,7 @@ import TravelAndPlaces from '@assets/images/emojiCategoryIcons/plane.svg'; import AnimalsAndNature from '@assets/images/emojiCategoryIcons/plant.svg'; import Activities from '@assets/images/emojiCategoryIcons/soccer-ball.svg'; import FrequentlyUsed from '@assets/images/history.svg'; -import {HeaderEmoji, PickerEmojis} from './types'; +import type {HeaderEmoji, PickerEmojis} from './types'; const skinTones = [ { diff --git a/assets/emojis/en.ts b/assets/emojis/en.ts index 0a1ca7611117..28051e5ecd99 100644 --- a/assets/emojis/en.ts +++ b/assets/emojis/en.ts @@ -1,4 +1,4 @@ -import {EmojisList} from './types'; +import type {EmojisList} from './types'; /* eslint-disable @typescript-eslint/naming-convention */ const enEmojis: EmojisList = { diff --git a/assets/emojis/es.ts b/assets/emojis/es.ts index 46f825643859..0d23f887f556 100644 --- a/assets/emojis/es.ts +++ b/assets/emojis/es.ts @@ -1,4 +1,4 @@ -import {EmojisList} from './types'; +import type {EmojisList} from './types'; /* eslint-disable @typescript-eslint/naming-convention */ const esEmojis: EmojisList = { diff --git a/assets/emojis/index.ts b/assets/emojis/index.ts index bc62adc4b252..02328001674e 100644 --- a/assets/emojis/index.ts +++ b/assets/emojis/index.ts @@ -1,10 +1,13 @@ +import type {Locale} from '@src/types/onyx'; import emojis from './common'; import enEmojis from './en'; import esEmojis from './es'; -import {Emoji} from './types'; +import type {Emoji, EmojisList} from './types'; type EmojiTable = Record; +type LocaleEmojis = Partial>; + const emojiNameTable = emojis.reduce((prev, cur) => { const newValue = prev; if (!('header' in cur) && cur.name) { @@ -26,10 +29,10 @@ const emojiCodeTableWithSkinTones = emojis.reduce((prev, cur) => { return newValue; }, {}); -const localeEmojis = { +const localeEmojis: LocaleEmojis = { en: enEmojis, es: esEmojis, -} as const; +}; export default emojis; export {emojiNameTable, emojiCodeTableWithSkinTones, localeEmojis}; diff --git a/assets/emojis/types.ts b/assets/emojis/types.ts index c3a3b692f202..e659924a7fa4 100644 --- a/assets/emojis/types.ts +++ b/assets/emojis/types.ts @@ -1,9 +1,9 @@ -import IconAsset from '@src/types/utils/IconAsset'; +import type IconAsset from '@src/types/utils/IconAsset'; type Emoji = { code: string; name: string; - types?: string[]; + types?: readonly string[]; }; type HeaderEmoji = { @@ -12,8 +12,10 @@ type HeaderEmoji = { code: string; }; -type PickerEmojis = Array; +type PickerEmoji = Emoji | HeaderEmoji; + +type PickerEmojis = PickerEmoji[]; type EmojisList = Record; -export type {Emoji, HeaderEmoji, EmojisList, PickerEmojis}; +export type {Emoji, HeaderEmoji, EmojisList, PickerEmojis, PickerEmoji}; diff --git a/contributingGuides/TS_STYLE.md b/contributingGuides/TS_STYLE.md index a583941bf71d..b60c28147a45 100644 --- a/contributingGuides/TS_STYLE.md +++ b/contributingGuides/TS_STYLE.md @@ -26,6 +26,7 @@ - [1.19 Satisfies operator](#satisfies-operator) - [1.20 Hooks instead of HOCs](#hooks-instead-of-hocs) - [1.21 `compose` usage](#compose-usage) + - [1.22 Type imports](#type-imports) - [Exception to Rules](#exception-to-rules) - [Communication Items](#communication-items) - [Migration Guidelines](#migration-guidelines) @@ -383,7 +384,7 @@ type Foo = { -- [1.15](#file-organization) **File organization**: In modules with platform-specific implementations, create `types.ts` to define shared types. Import and use shared types in each platform specific files. Do not use [`satisfies` operator](#satisfies-operator) for platform-specific implementations, always define shared types that complies with all variants. +- [1.15](#file-organization) **File organization**: In modules with platform-specific implementations, create `types.ts` to define shared types. Import and use shared types in each platform specific files. Do not use [`satisfies` operator](#satisfies-operator) for platform-specific implementations, always define shared types that complies with all variants. > Why? To encourage consistent API across platform-specific implementations. If you're migrating module that doesn't have a default implement (i.e. `index.ts`, e.g. `getPlatform`), refer to [Migration Guidelines](#migration-guidelines) for further information. @@ -514,7 +515,7 @@ type Foo = { - [1.20](#hooks-instead-of-hocs) **Hooks instead of HOCs**: Replace HOCs usage with Hooks whenever possible. - + > Why? Hooks are easier to use (can be used inside the function component), and don't need nesting or `compose` when exporting the component. It also allows us to remove `compose` completely in some components since it has been bringing up some issues with TypeScript. Read the [`compose` usage](#compose-usage) section for further information about the TypeScript issues with `compose`. > Note: Because Onyx doesn't provide a hook yet, in a component that accesses Onyx data with `withOnyx` HOC, please make sure that you don't use other HOCs (if applicable) to avoid HOC nesting. @@ -571,7 +572,7 @@ type Foo = { - [1.21](#compose-usage) **`compose` usage**: Avoid the usage of `compose` function to compose HOCs in TypeScript files. Use nesting instead. - + > Why? `compose` function doesn't work well with TypeScript when dealing with several HOCs being used in a component, many times resulting in wrong types and errors. Instead, nesting can be used to allow a seamless use of multiple HOCs and result in a correct return type of the compoment. Also, you can use [hooks instead of HOCs](#hooks-instead-of-hocs) whenever possible to minimize or even remove the need of HOCs in the component. ```ts @@ -607,6 +608,38 @@ type Foo = { export default withCurrentUserPersonalDetails(ComponentWithReportOrNotFound); ``` + + +- [1.22](#type-imports) **Type imports/exports**: Always use the `type` keyword when importing/exporting types + + > Why? In order to improve code clarity and consistency and reduce bundle size after typescript transpilation, we enforce the all type imports/exports to contain the `type` keyword. This way, TypeScript can automatically remove those imports from the transpiled JavaScript bundle + + Imports: + ```ts + // BAD + import {SomeType} from './a' + import someVariable from './a' + + import {someVariable, SomeOtherType} from './b' + + // GOOD + import type {SomeType} from './a' + import someVariable from './a' + ``` + + Exports: + ```ts + // BAD + export {SomeType} + export someVariable + // or + export {someVariable, SomeOtherType} + + // GOOD + export type {SomeType} + export someVariable + ``` + ## Exception to Rules Most of the rules are enforced in ESLint or checked by TypeScript. If you think your particular situation warrants an exception, post the context in the `#expensify-open-source` Slack channel with your message prefixed with `TS EXCEPTION:`. The internal engineer assigned to the PR should be the one that approves each exception, however all discussion regarding granting exceptions should happen in the public channel instead of the GitHub PR page so that the TS migration team can access them easily. diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index f267261a49c0..ecf3b8c9cad9 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.21 + 1.4.22 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.21.1 + 1.4.22.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index f95a3f871d4c..1ec1aeb6ce14 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.21 + 1.4.22 CFBundleSignature ???? CFBundleVersion - 1.4.21.1 + 1.4.22.0 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 77c390c46416..ee54d98895a5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -614,7 +614,7 @@ PODS: - React-Core - react-native-pager-view (6.2.0): - React-Core - - react-native-pdf (6.7.3): + - react-native-pdf (6.7.4): - React-Core - react-native-performance (5.1.0): - React-Core @@ -1265,7 +1265,7 @@ SPEC CHECKSUMS: react-native-key-command: 5af6ee30ff4932f78da6a2109017549042932aa5 react-native-netinfo: ccbe1085dffd16592791d550189772e13bf479e2 react-native-pager-view: 0ccb8bf60e2ebd38b1f3669fa3650ecce81db2df - react-native-pdf: b4ca3d37a9a86d9165287741c8b2ef4d8940c00e + react-native-pdf: 79aa75e39a80c1d45ffe58aa500f3cf08f267a2e react-native-performance: cef2b618d47b277fb5c3280b81a3aad1e72f2886 react-native-plaid-link-sdk: df1618a85a615d62ff34e34b76abb7a56497fbc1 react-native-quick-sqlite: bcc7a7a250a40222f18913a97cd356bf82d0a6c4 diff --git a/package-lock.json b/package-lock.json index c8b4b2ba2082..2c1da1670e19 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.21-1", + "version": "1.4.22-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.21-1", + "version": "1.4.22-0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -96,10 +96,10 @@ "react-native-modal": "^13.0.0", "react-native-onyx": "1.0.118", "react-native-pager-view": "^6.2.0", - "react-native-pdf": "^6.7.3", + "react-native-pdf": "^6.7.4", "react-native-performance": "^5.1.0", "react-native-permissions": "^3.9.3", - "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#0d15d4618f58e99c1261921111e68ee85bb3c2a8", + "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#7a407cd4174d9838a944c1c2e1cb4a9737ac69c5", "react-native-plaid-link-sdk": "10.8.0", "react-native-qrcode-svg": "^6.2.0", "react-native-quick-sqlite": "^8.0.0-beta.2", @@ -201,6 +201,7 @@ "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-expensify": "^2.0.43", "eslint-config-prettier": "^8.8.0", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^24.1.0", "eslint-plugin-jsdoc": "^46.2.6", "eslint-plugin-jsx-a11y": "^6.6.1", @@ -21202,8 +21203,7 @@ "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@types/keyv": { "version": "3.1.4", @@ -23403,15 +23403,15 @@ "license": "MIT" }, "node_modules/array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", "is-string": "^1.0.7" }, "engines": { @@ -23448,15 +23448,34 @@ "node": ">=0.10.0" } }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", + "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" }, "engines": { @@ -23467,14 +23486,14 @@ } }, "node_modules/array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" }, "engines": { @@ -30582,12 +30601,14 @@ } }, "node_modules/eslint-import-resolver-node": { - "version": "0.3.6", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^3.2.7", - "resolve": "^1.20.0" + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, "node_modules/eslint-import-resolver-node/node_modules/debug": { @@ -30595,15 +30616,15 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "license": "MIT", "dependencies": { "ms": "^2.1.1" } }, "node_modules/eslint-module-utils": { - "version": "2.7.4", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", "dev": true, - "license": "MIT", "dependencies": { "debug": "^3.2.7" }, @@ -30621,7 +30642,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "license": "MIT", "dependencies": { "ms": "^2.1.1" } @@ -30719,23 +30739,28 @@ } }, "node_modules/eslint-plugin-import": { - "version": "2.26.0", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, - "license": "MIT", "dependencies": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", - "has": "^1.0.3", - "is-core-module": "^2.8.1", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" }, "engines": { "node": ">=4" @@ -30745,11 +30770,12 @@ } }, "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "2.6.9", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "license": "MIT", "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "node_modules/eslint-plugin-import/node_modules/doctrine": { @@ -30765,12 +30791,14 @@ "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-import/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, - "license": "MIT" + "bin": { + "semver": "bin/semver.js" + } }, "node_modules/eslint-plugin-jest": { "version": "24.7.0", @@ -33493,10 +33521,12 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "license": "MIT" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/function.prototype.name": { "version": "1.1.5", @@ -34214,6 +34244,17 @@ "minimalistic-assert": "^1.0.1" } }, + "node_modules/hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hast-to-hyperscript": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz", @@ -35900,11 +35941,11 @@ } }, "node_modules/is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.0" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -44759,14 +44800,14 @@ } }, "node_modules/object.fromentries": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", - "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -44792,6 +44833,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/object.groupby": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", + "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1" + } + }, "node_modules/object.hasown": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", @@ -44819,14 +44872,14 @@ } }, "node_modules/object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", "dev": true, "dependencies": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" }, "engines": { "node": ">= 0.4" @@ -47582,9 +47635,9 @@ } }, "node_modules/react-native-pdf": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.3.tgz", - "integrity": "sha512-bK1fVkj18kBA5YlRFNJ3/vJ1bEX3FDHyAPY6ArtIdVs+vv0HzcK5WH9LSd2bxUsEMIyY9CSjP4j8BcxNXTiQkQ==", + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.4.tgz", + "integrity": "sha512-sBeNcsrTRnLjmiU9Wx7Uk0K2kPSQtKIIG+FECdrEG16TOdtmQ3iqqEwt0dmy0pJegpg07uES5BXqiKsKkRUIFw==", "dependencies": { "crypto-js": "4.2.0", "deprecated-react-native-prop-types": "^2.3.0" @@ -47631,8 +47684,8 @@ }, "node_modules/react-native-picker-select": { "version": "8.1.0", - "resolved": "git+ssh://git@github.com/Expensify/react-native-picker-select.git#0d15d4618f58e99c1261921111e68ee85bb3c2a8", - "integrity": "sha512-ly0ZCt3K4RX7t9lfSb2OSGAw0cv8UqdMoxNfh5j+KujYYq+N8VsI9O/lmqquNeX/AMp5hM3fjetEWue4nZw/hA==", + "resolved": "git+ssh://git@github.com/Expensify/react-native-picker-select.git#7a407cd4174d9838a944c1c2e1cb4a9737ac69c5", + "integrity": "sha512-NpXXyK+UuANYOysjUb9pCoq9SookRYPfpOcM4shxOD4+2Fkh7TYt2LBUpAdBicMHmtaR43RWXVQk9pMimOhg2w==", "license": "MIT", "dependencies": { "lodash.isequal": "^4.5.0" @@ -53277,12 +53330,13 @@ "license": "Apache-2.0" }, "node_modules/tsconfig-paths": { - "version": "3.14.1", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, - "license": "MIT", "dependencies": { "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } @@ -53304,7 +53358,6 @@ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", "dev": true, - "license": "MIT", "engines": { "node": ">=4" } @@ -73074,15 +73127,15 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, "array-includes": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz", - "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==", + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.7.tgz", + "integrity": "sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", - "get-intrinsic": "^1.1.3", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1", "is-string": "^1.0.7" } }, @@ -73101,27 +73154,40 @@ "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", "devOptional": true }, + "array.prototype.findlastindex": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.3.tgz", + "integrity": "sha512-LzLoiOMAxvy+Gd3BAq3B7VeIgPdo+Q8hthvKtXybMvRV0jrXfJM/t8mw7nNlpEcVlVUnCnM2KSX4XU5HmpodOA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0", + "get-intrinsic": "^1.2.1" + } + }, "array.prototype.flat": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.1.tgz", - "integrity": "sha512-roTU0KWIOmJ4DRLmwKd19Otg0/mT3qPNt0Qb3GWW8iObuZXxrjB/pzn0R3hqpRSWg4HCwqx+0vwOnWnvlOyeIA==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" } }, "array.prototype.flatmap": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz", - "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==", + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", "es-shim-unscopables": "^1.0.0" } }, @@ -78429,11 +78495,14 @@ "requires": {} }, "eslint-import-resolver-node": { - "version": "0.3.6", + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, "requires": { "debug": "^3.2.7", - "resolve": "^1.20.0" + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" }, "dependencies": { "debug": { @@ -78448,7 +78517,9 @@ } }, "eslint-module-utils": { - "version": "2.7.4", + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.0.tgz", + "integrity": "sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==", "dev": true, "requires": { "debug": "^3.2.7" @@ -78520,29 +78591,37 @@ } }, "eslint-plugin-import": { - "version": "2.26.0", + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", + "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", "dev": true, "requires": { - "array-includes": "^3.1.4", - "array.prototype.flat": "^1.2.5", - "debug": "^2.6.9", + "array-includes": "^3.1.7", + "array.prototype.findlastindex": "^1.2.3", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.6", - "eslint-module-utils": "^2.7.3", - "has": "^1.0.3", - "is-core-module": "^2.8.1", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.8.0", + "hasown": "^2.0.0", + "is-core-module": "^2.13.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.values": "^1.1.5", - "resolve": "^1.22.0", - "tsconfig-paths": "^3.14.1" + "object.fromentries": "^2.0.7", + "object.groupby": "^1.0.1", + "object.values": "^1.1.7", + "semver": "^6.3.1", + "tsconfig-paths": "^3.15.0" }, "dependencies": { "debug": { - "version": "2.6.9", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "requires": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, "doctrine": { @@ -78554,10 +78633,10 @@ "esutils": "^2.0.2" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true } } @@ -80424,9 +80503,9 @@ "optional": true }, "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "function.prototype.name": { "version": "1.1.5", @@ -80914,6 +80993,14 @@ "minimalistic-assert": "^1.0.1" } }, + "hasown": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", + "requires": { + "function-bind": "^1.1.2" + } + }, "hast-to-hyperscript": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/hast-to-hyperscript/-/hast-to-hyperscript-9.0.1.tgz", @@ -82096,11 +82183,11 @@ } }, "is-core-module": { - "version": "2.13.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.0.tgz", - "integrity": "sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==", + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", "requires": { - "has": "^1.0.3" + "hasown": "^2.0.0" } }, "is-data-descriptor": { @@ -88377,14 +88464,14 @@ } }, "object.fromentries": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz", - "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==", + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.7.tgz", + "integrity": "sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" } }, "object.getownpropertydescriptors": { @@ -88397,6 +88484,18 @@ "es-abstract": "^1.20.1" } }, + "object.groupby": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.1.tgz", + "integrity": "sha512-HqaQtqLnp/8Bn4GL16cj+CUYbnpe1bh0TtEaWvybszDG4tgxCJuRpV8VGuvNaI1fAnI4lUJzDG55MXcOH4JZcQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "get-intrinsic": "^1.2.1" + } + }, "object.hasown": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.2.tgz", @@ -88417,14 +88516,14 @@ } }, "object.values": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz", - "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==", + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.7.tgz", + "integrity": "sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==", "dev": true, "requires": { "call-bind": "^1.0.2", - "define-properties": "^1.1.4", - "es-abstract": "^1.20.4" + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1" } }, "objectorarray": { @@ -90432,9 +90531,9 @@ "requires": {} }, "react-native-pdf": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.3.tgz", - "integrity": "sha512-bK1fVkj18kBA5YlRFNJ3/vJ1bEX3FDHyAPY6ArtIdVs+vv0HzcK5WH9LSd2bxUsEMIyY9CSjP4j8BcxNXTiQkQ==", + "version": "6.7.4", + "resolved": "https://registry.npmjs.org/react-native-pdf/-/react-native-pdf-6.7.4.tgz", + "integrity": "sha512-sBeNcsrTRnLjmiU9Wx7Uk0K2kPSQtKIIG+FECdrEG16TOdtmQ3iqqEwt0dmy0pJegpg07uES5BXqiKsKkRUIFw==", "requires": { "crypto-js": "4.2.0", "deprecated-react-native-prop-types": "^2.3.0" @@ -90460,9 +90559,9 @@ "requires": {} }, "react-native-picker-select": { - "version": "git+ssh://git@github.com/Expensify/react-native-picker-select.git#0d15d4618f58e99c1261921111e68ee85bb3c2a8", - "integrity": "sha512-ly0ZCt3K4RX7t9lfSb2OSGAw0cv8UqdMoxNfh5j+KujYYq+N8VsI9O/lmqquNeX/AMp5hM3fjetEWue4nZw/hA==", - "from": "react-native-picker-select@git+https://github.com/Expensify/react-native-picker-select.git#0d15d4618f58e99c1261921111e68ee85bb3c2a8", + "version": "git+ssh://git@github.com/Expensify/react-native-picker-select.git#7a407cd4174d9838a944c1c2e1cb4a9737ac69c5", + "integrity": "sha512-NpXXyK+UuANYOysjUb9pCoq9SookRYPfpOcM4shxOD4+2Fkh7TYt2LBUpAdBicMHmtaR43RWXVQk9pMimOhg2w==", + "from": "react-native-picker-select@git+https://github.com/Expensify/react-native-picker-select.git#7a407cd4174d9838a944c1c2e1cb4a9737ac69c5", "requires": { "lodash.isequal": "^4.5.0" } @@ -94475,11 +94574,13 @@ "integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==" }, "tsconfig-paths": { - "version": "3.14.1", + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "requires": { "@types/json5": "^0.0.29", - "json5": "^1.0.1", + "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" }, diff --git a/package.json b/package.json index bd4b8ffacedf..118f63e45879 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.21-1", + "version": "1.4.22-0", "author": "Expensify, Inc.", "homepage": "https://new.expensify.com", "description": "New Expensify is the next generation of Expensify: a reimagination of payments based atop a foundation of chat.", @@ -144,10 +144,10 @@ "react-native-modal": "^13.0.0", "react-native-onyx": "1.0.118", "react-native-pager-view": "^6.2.0", - "react-native-pdf": "^6.7.3", + "react-native-pdf": "^6.7.4", "react-native-performance": "^5.1.0", "react-native-permissions": "^3.9.3", - "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#0d15d4618f58e99c1261921111e68ee85bb3c2a8", + "react-native-picker-select": "git+https://github.com/Expensify/react-native-picker-select.git#7a407cd4174d9838a944c1c2e1cb4a9737ac69c5", "react-native-plaid-link-sdk": "10.8.0", "react-native-qrcode-svg": "^6.2.0", "react-native-quick-sqlite": "^8.0.0-beta.2", @@ -249,6 +249,7 @@ "eslint-config-airbnb-typescript": "^17.1.0", "eslint-config-expensify": "^2.0.43", "eslint-config-prettier": "^8.8.0", + "eslint-plugin-import": "^2.29.1", "eslint-plugin-jest": "^24.1.0", "eslint-plugin-jsdoc": "^46.2.6", "eslint-plugin-jsx-a11y": "^6.6.1", diff --git a/src/CONFIG.ts b/src/CONFIG.ts index 61e347671269..37da65f0c305 100644 --- a/src/CONFIG.ts +++ b/src/CONFIG.ts @@ -1,5 +1,6 @@ import {Platform} from 'react-native'; -import Config, {NativeConfig} from 'react-native-config'; +import type {NativeConfig} from 'react-native-config'; +import Config from 'react-native-config'; import CONST from './CONST'; import getPlatform from './libs/getPlatform'; import * as Url from './libs/Url'; diff --git a/src/CONST.ts b/src/CONST.ts index 865e97cc0133..2fd592f539c2 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -103,6 +103,10 @@ const CONST = { MERCHANT_NAME_MAX_LENGTH: 255, + REQUEST_PREVIEW: { + MAX_LENGTH: 83, + }, + CALENDAR_PICKER: { // Numbers were arbitrarily picked. MIN_YEAR: CURRENT_YEAR - 100, @@ -512,6 +516,7 @@ const CONST = { CLOSED: 'CLOSED', CREATED: 'CREATED', IOU: 'IOU', + MARKEDREIMBURSED: 'MARKEDREIMBURSED', MODIFIEDEXPENSE: 'MODIFIEDEXPENSE', MOVED: 'MOVED', REIMBURSEMENTQUEUED: 'REIMBURSEMENTQUEUED', @@ -1176,7 +1181,6 @@ const CONST = { EXPENSIFY: 'Expensify', VBBA: 'ACH', }, - DEFAULT_AMOUNT: 0, TYPE: { SEND: 'send', SPLIT: 'split', @@ -2945,6 +2949,7 @@ const CONST = { PARENT_CHILD_SEPARATOR: ': ', CATEGORY_LIST_THRESHOLD: 8, TAG_LIST_THRESHOLD: 8, + TAX_RATES_LIST_THRESHOLD: 8, COLON: ':', MAPBOX: { PADDING: 50, @@ -3053,6 +3058,42 @@ const CONST = { CAROUSEL: 3, }, + VIOLATIONS: { + ALL_TAG_LEVELS_REQUIRED: 'allTagLevelsRequired', + AUTO_REPORTED_REJECTED_EXPENSE: 'autoReportedRejectedExpense', + BILLABLE_EXPENSE: 'billableExpense', + CASH_EXPENSE_WITH_NO_RECEIPT: 'cashExpenseWithNoReceipt', + CATEGORY_OUT_OF_POLICY: 'categoryOutOfPolicy', + CONVERSION_SURCHARGE: 'conversionSurcharge', + CUSTOM_UNIT_OUT_OF_POLICY: 'customUnitOutOfPolicy', + DUPLICATED_TRANSACTION: 'duplicatedTransaction', + FIELD_REQUIRED: 'fieldRequired', + FUTURE_DATE: 'futureDate', + INVOICE_MARKUP: 'invoiceMarkup', + MAX_AGE: 'maxAge', + MISSING_CATEGORY: 'missingCategory', + MISSING_COMMENT: 'missingComment', + MISSING_TAG: 'missingTag', + MODIFIED_AMOUNT: 'modifiedAmount', + MODIFIED_DATE: 'modifiedDate', + NON_EXPENSIWORKS_EXPENSE: 'nonExpensiworksExpense', + OVER_AUTO_APPROVAL_LIMIT: 'overAutoApprovalLimit', + OVER_CATEGORY_LIMIT: 'overCategoryLimit', + OVER_LIMIT: 'overLimit', + OVER_LIMIT_ATTENDEE: 'overLimitAttendee', + PER_DAY_LIMIT: 'perDayLimit', + RECEIPT_NOT_SMART_SCANNED: 'receiptNotSmartScanned', + RECEIPT_REQUIRED: 'receiptRequired', + RTER: 'rter', + SMARTSCAN_FAILED: 'smartscanFailed', + SOME_TAG_LEVELS_REQUIRED: 'someTagLevelsRequired', + TAG_OUT_OF_POLICY: 'tagOutOfPolicy', + TAX_AMOUNT_CHANGED: 'taxAmountChanged', + TAX_OUT_OF_POLICY: 'taxOutOfPolicy', + TAX_RATE_CHANGED: 'taxRateChanged', + TAX_REQUIRED: 'taxRequired', + }, + /** Context menu types */ CONTEXT_MENU_TYPES: { LINK: 'LINK', diff --git a/src/NAVIGATORS.ts b/src/NAVIGATORS.ts index a3a041e65684..c68a950d3501 100644 --- a/src/NAVIGATORS.ts +++ b/src/NAVIGATORS.ts @@ -4,6 +4,7 @@ * */ export default { CENTRAL_PANE_NAVIGATOR: 'CentralPaneNavigator', + LEFT_MODAL_NAVIGATOR: 'LeftModalNavigator', RIGHT_MODAL_NAVIGATOR: 'RightModalNavigator', FULL_SCREEN_NAVIGATOR: 'FullScreenNavigator', } as const; diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 53cd37e71f67..89ddbdc06883 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -1,8 +1,8 @@ -import {OnyxEntry} from 'react-native-onyx/lib/types'; -import {ValueOf} from 'type-fest'; -import CONST from './CONST'; -import * as OnyxTypes from './types/onyx'; -import DeepValueOf from './types/utils/DeepValueOf'; +import type {OnyxEntry} from 'react-native-onyx/lib/types'; +import type {ValueOf} from 'type-fest'; +import type CONST from './CONST'; +import type * as OnyxTypes from './types/onyx'; +import type DeepValueOf from './types/utils/DeepValueOf'; /** * This is a file containing constants for all the top level keys in our store @@ -250,6 +250,7 @@ const ONYXKEYS = { POLICY_CATEGORIES: 'policyCategories_', POLICY_RECENTLY_USED_CATEGORIES: 'policyRecentlyUsedCategories_', POLICY_TAGS: 'policyTags_', + POLICY_TAX_RATE: 'policyTaxRates_', POLICY_RECENTLY_USED_TAGS: 'policyRecentlyUsedTags_', POLICY_REPORT_FIELDS: 'policyReportFields_', POLICY_RECENTLY_USED_REPORT_FIELDS: 'policyRecentlyUsedReportFields_', diff --git a/src/ROUTES.ts b/src/ROUTES.ts index db17378684d6..1663f0295b9e 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -1,5 +1,5 @@ -import {IsEqual, ValueOf} from 'type-fest'; -import CONST from './CONST'; +import type {IsEqual, ValueOf} from 'type-fest'; +import type CONST from './CONST'; // This is a file containing constants for all the routes we want to be able to go to @@ -322,6 +322,16 @@ const ROUTES = { getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => getUrlWithBackToParam(`create/${iouType}/amount/${transactionID}/${reportID}/`, backTo), }, + MONEY_REQUEST_STEP_TAX_RATE: { + route: 'create/:iouType/taxRate/:transactionID/:reportID?', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo: string) => + getUrlWithBackToParam(`create/${iouType}/taxRate/${transactionID}/${reportID}`, backTo), + }, + MONEY_REQUEST_STEP_TAX_AMOUNT: { + route: 'create/:iouType/taxAmount/:transactionID/:reportID?', + getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo: string) => + getUrlWithBackToParam(`create/${iouType}/taxAmount/${transactionID}/${reportID}`, backTo), + }, MONEY_REQUEST_STEP_CATEGORY: { route: 'create/:iouType/category/:transactionID/:reportID/', getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => @@ -348,9 +358,9 @@ const ROUTES = { getUrlWithBackToParam(`create/${iouType}/distance/${transactionID}/${reportID}/`, backTo), }, MONEY_REQUEST_STEP_MERCHANT: { - route: 'create/:iouType/merchante/:transactionID/:reportID/', + route: 'create/:iouType/merchant/:transactionID/:reportID/', getRoute: (iouType: ValueOf, transactionID: string, reportID: string, backTo = '') => - getUrlWithBackToParam(`create/${iouType}/merchante/${transactionID}/${reportID}/`, backTo), + getUrlWithBackToParam(`create/${iouType}/merchant/${transactionID}/${reportID}/`, backTo), }, MONEY_REQUEST_STEP_PARTICIPANTS: { route: 'create/:iouType/participants/:transactionID/:reportID/', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index c1d2059cd3b0..91c4153bacd2 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -2,7 +2,7 @@ * This is a file containing constants for all of the screen names. In most cases, we should use the routes for * navigation. But there are situations where we may need to access screen names directly. */ -import DeepValueOf from './types/utils/DeepValueOf'; +import type DeepValueOf from './types/utils/DeepValueOf'; const PROTECTED_SCREENS = { HOME: 'Home', @@ -81,10 +81,12 @@ const SCREENS = { SAVE_THE_WORLD: { ROOT: 'SaveTheWorld_Root', }, + LEFT_MODAL: { + SEARCH: 'Search', + }, RIGHT_MODAL: { SETTINGS: 'Settings', NEW_CHAT: 'NewChat', - SEARCH: 'Search', DETAILS: 'Details', PROFILE: 'Profile', REPORT_DETAILS: 'Report_Details', @@ -130,6 +132,8 @@ const SCREENS = { STEP_SCAN: 'Money_Request_Step_Scan', STEP_TAG: 'Money_Request_Step_Tag', STEP_WAYPOINT: 'Money_Request_Step_Waypoint', + STEP_TAX_AMOUNT: 'Money_Request_Step_Tax_Amount', + STEP_TAX_RATE: 'Money_Request_Step_Tax_Rate', ROOT: 'Money_Request', AMOUNT: 'Money_Request_Amount', PARTICIPANTS: 'Money_Request_Participants', diff --git a/src/TIMEZONES.ts b/src/TIMEZONES.ts index 1eb49f291495..238563134872 100644 --- a/src/TIMEZONES.ts +++ b/src/TIMEZONES.ts @@ -1,5 +1,6 @@ +/* eslint-disable @typescript-eslint/naming-convention */ // All timezones were taken from: https://raw.githubusercontent.com/leon-do/Timezones/main/timezone.json -export default [ +const TIMEZONES = [ 'Africa/Abidjan', 'Africa/Accra', 'Africa/Addis_Ababa', @@ -419,3 +420,137 @@ export default [ 'Pacific/Wake', 'Pacific/Wallis', ] as const; + +/** + * The timezones supported in browser and on native devices differ, so we must map each timezone to its supported equivalent. + * Data sourced from https://en.wikipedia.org/wiki/List_of_tz_database_time_zones + */ +const timezoneBackwardMap: Record = { + 'Africa/Asmera': 'Africa/Nairobi', + 'Africa/Timbuktu': 'Africa/Abidjan', + 'America/Argentina/ComodRivadavia': 'America/Argentina/Catamarca', + 'America/Atka': 'America/Adak', + 'America/Buenos_Aires': 'America/Argentina/Buenos_Aires', + 'America/Catamarca': 'America/Argentina/Catamarca', + 'America/Coral_Harbour': 'America/Panama', + 'America/Cordoba': 'America/Argentina/Cordoba', + 'America/Ensenada': 'America/Tijuana', + 'America/Fort_Wayne': 'America/Indiana/Indianapolis', + 'America/Godthab': 'America/Nuuk', + 'America/Indianapolis': 'America/Indiana/Indianapolis', + 'America/Jujuy': 'America/Argentina/Jujuy', + 'America/Knox_IN': 'America/Indiana/Knox', + 'America/Louisville': 'America/Kentucky/Louisville', + 'America/Mendoza': 'America/Argentina/Mendoza', + 'America/Montreal': 'America/Toronto', + 'America/Nipigon': 'America/Toronto', + 'America/Pangnirtung': 'America/Iqaluit', + 'America/Porto_Acre': 'America/Rio_Branco', + 'America/Rainy_River': 'America/Winnipeg', + 'America/Rosario': 'America/Argentina/Cordoba', + 'America/Santa_Isabel': 'America/Tijuana', + 'America/Shiprock': 'America/Denver', + 'America/Thunder_Bay': 'America/Toronto', + 'America/Virgin': 'America/Puerto_Rico', + 'America/Yellowknife': 'America/Edmonton', + 'Antarctica/South_Pole': 'Pacific/Auckland', + 'Asia/Ashkhabad': 'Asia/Ashgabat', + 'Asia/Calcutta': 'Asia/Kolkata', + 'Asia/Chongqing': 'Asia/Shanghai', + 'Asia/Chungking': 'Asia/Shanghai', + 'Asia/Dacca': 'Asia/Dhaka', + 'Asia/Harbin': 'Asia/Shanghai', + 'Asia/Istanbul': 'Europe/Istanbul', + 'Asia/Kashgar': 'Asia/Urumqi', + 'Asia/Katmandu': 'Asia/Kathmandu', + 'Asia/Macao': 'Asia/Macau', + 'Asia/Rangoon': 'Asia/Yangon', + 'Asia/Saigon': 'Asia/Ho_Chi_Minh', + 'Asia/Tel_Aviv': 'Asia/Jerusalem', + 'Asia/Thimbu': 'Asia/Thimphu', + 'Asia/Ujung_Pandang': 'Asia/Makassar', + 'Asia/Ulan_Bator': 'Asia/Ulaanbaatar', + 'Atlantic/Faeroe': 'Atlantic/Faroe', + 'Atlantic/Jan_Mayen': 'Europe/Berlin', + 'Australia/ACT': 'Australia/Sydney', + 'Australia/Canberra': 'Australia/Sydney', + 'Australia/Currie': 'Australia/Hobart', + 'Australia/LHI': 'Australia/Lord_Howe', + 'Australia/NSW': 'Australia/Sydney', + 'Australia/North': 'Australia/Darwin', + 'Australia/Queensland': 'Australia/Brisbane', + 'Australia/South': 'Australia/Adelaide', + 'Australia/Tasmania': 'Australia/Hobart', + 'Australia/Victoria': 'Australia/Melbourne', + 'Australia/West': 'Australia/Perth', + 'Australia/Yancowinna': 'Australia/Broken_Hill', + 'Brazil/Acre': 'America/Rio_Branco', + 'Brazil/DeNoronha': 'America/Noronha', + 'Brazil/East': 'America/Sao_Paulo', + 'Brazil/West': 'America/Manaus', + 'Canada/Atlantic': 'America/Halifax', + 'Canada/Central': 'America/Winnipeg', + 'Canada/Eastern': 'America/Toronto', + 'Canada/Mountain': 'America/Edmonton', + 'Canada/Newfoundland': 'America/St_Johns', + 'Canada/Pacific': 'America/Vancouver', + 'Canada/Saskatchewan': 'America/Regina', + 'Canada/Yukon': 'America/Whitehorse', + 'Chile/Continental': 'America/Santiago', + 'Chile/EasterIsland': 'Pacific/Easter', + Cuba: 'America/Havana', + Egypt: 'Africa/Cairo', + Eire: 'Europe/Dublin', + 'Europe/Belfast': 'Europe/London', + 'Europe/Kiev': 'Europe/Kyiv', + 'Europe/Nicosia': 'Asia/Nicosia', + 'Europe/Tiraspol': 'Europe/Chisinau', + 'Europe/Uzhgorod': 'Europe/Kyiv', + 'Europe/Zaporozhye': 'Europe/Kyiv', + GB: 'Europe/London', + 'GB-Eire': 'Europe/London', + Hongkong: 'Asia/Hong_Kong', + Iceland: 'Africa/Abidjan', + Iran: 'Asia/Tehran', + Israel: 'Asia/Jerusalem', + Jamaica: 'America/Jamaica', + Japan: 'Asia/Tokyo', + Kwajalein: 'Pacific/Kwajalein', + Libya: 'Africa/Tripoli', + 'Mexico/BajaNorte': 'America/Tijuana', + 'Mexico/BajaSur': 'America/Mazatlan', + 'Mexico/General': 'America/Mexico_City', + NZ: 'Pacific/Auckland', + 'NZ-CHAT': 'Pacific/Chatham', + Navajo: 'America/Denver', + PRC: 'Asia/Shanghai', + 'Pacific/Enderbury': 'Pacific/Kanton', + 'Pacific/Johnston': 'Pacific/Honolulu', + 'Pacific/Ponape': 'Pacific/Guadalcanal', + 'Pacific/Samoa': 'Pacific/Pago_Pago', + 'Pacific/Truk': 'Pacific/Port_Moresby', + 'Pacific/Yap': 'Pacific/Port_Moresby', + Poland: 'Europe/Warsaw', + Portugal: 'Europe/Lisbon', + ROC: 'Asia/Taipei', + ROK: 'Asia/Seoul', + Singapore: 'Asia/Singapore', + Turkey: 'Europe/Istanbul', + 'US/Alaska': 'America/Anchorage', + 'US/Aleutian': 'America/Adak', + 'US/Arizona': 'America/Phoenix', + 'US/Central': 'America/Chicago', + 'US/East-Indiana': 'America/Indiana/Indianapolis', + 'US/Eastern': 'America/New_York', + 'US/Hawaii': 'Pacific/Honolulu', + 'US/Indiana-Starke': 'America/Indiana/Knox', + 'US/Michigan': 'America/Detroit', + 'US/Mountain': 'America/Denver', + 'US/Pacific': 'America/Los_Angeles', + 'US/Samoa': 'Pacific/Pago_Pago', + 'W-SU': 'Europe/Moscow', +}; + +export {timezoneBackwardMap}; + +export default TIMEZONES; diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 31b04a3d954f..9f2635633318 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -349,6 +349,7 @@ function AddressSearch({ lat: successData.coords.latitude, lng: successData.coords.longitude, address: CONST.YOUR_LOCATION_TEXT, + name: CONST.YOUR_LOCATION_TEXT, }; onPress(location); }, diff --git a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx index 1bcdbb383f7a..bb3792f59d9f 100644 --- a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx +++ b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.tsx @@ -1,6 +1,7 @@ import Str from 'expensify-common/lib/str'; import React, {useEffect, useRef} from 'react'; -import {Text as RNText, StyleSheet} from 'react-native'; +import type {Text as RNText} from 'react-native'; +import {StyleSheet} from 'react-native'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; diff --git a/src/components/AnchorForCommentsOnly/types.ts b/src/components/AnchorForCommentsOnly/types.ts index 005364c2077f..eea8571f5277 100644 --- a/src/components/AnchorForCommentsOnly/types.ts +++ b/src/components/AnchorForCommentsOnly/types.ts @@ -1,5 +1,5 @@ -import {StyleProp, TextStyle} from 'react-native'; -import ChildrenProps from '@src/types/utils/ChildrenProps'; +import type {StyleProp, TextStyle} from 'react-native'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; type AnchorForCommentsOnlyProps = ChildrenProps & { /** The URL to open */ diff --git a/src/components/AnimatedStep/AnimatedStepContext.ts b/src/components/AnimatedStep/AnimatedStepContext.ts index 3b4c5f79a34f..14dba4b27cc2 100644 --- a/src/components/AnimatedStep/AnimatedStepContext.ts +++ b/src/components/AnimatedStep/AnimatedStepContext.ts @@ -1,6 +1,7 @@ -import React, {createContext} from 'react'; -import {ValueOf} from 'type-fest'; -import CONST from '@src/CONST'; +import type React from 'react'; +import {createContext} from 'react'; +import type {ValueOf} from 'type-fest'; +import type CONST from '@src/CONST'; type AnimationDirection = ValueOf; diff --git a/src/components/AnimatedStep/AnimatedStepProvider.tsx b/src/components/AnimatedStep/AnimatedStepProvider.tsx index 53b3a0e0a53d..ea268e1d52cb 100644 --- a/src/components/AnimatedStep/AnimatedStepProvider.tsx +++ b/src/components/AnimatedStep/AnimatedStepProvider.tsx @@ -1,7 +1,8 @@ import React, {useMemo, useState} from 'react'; import CONST from '@src/CONST'; -import ChildrenProps from '@src/types/utils/ChildrenProps'; -import AnimatedStepContext, {AnimationDirection} from './AnimatedStepContext'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; +import type {AnimationDirection} from './AnimatedStepContext'; +import AnimatedStepContext from './AnimatedStepContext'; function AnimatedStepProvider({children}: ChildrenProps): React.ReactNode { const [animationDirection, setAnimationDirection] = useState(CONST.ANIMATION_DIRECTION.IN); diff --git a/src/components/AnimatedStep/index.tsx b/src/components/AnimatedStep/index.tsx index e2b9952c0617..66d2108ca5d8 100644 --- a/src/components/AnimatedStep/index.tsx +++ b/src/components/AnimatedStep/index.tsx @@ -1,11 +1,11 @@ import React, {useMemo} from 'react'; -import {StyleProp, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; import * as Animatable from 'react-native-animatable'; import useThemeStyles from '@hooks/useThemeStyles'; import useNativeDriver from '@libs/useNativeDriver'; import CONST from '@src/CONST'; -import ChildrenProps from '@src/types/utils/ChildrenProps'; -import {AnimationDirection} from './AnimatedStepContext'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; +import type {AnimationDirection} from './AnimatedStepContext'; type AnimatedStepProps = ChildrenProps & { /** Styles to be assigned to Container */ diff --git a/src/components/AnimatedStep/useAnimatedStepContext.ts b/src/components/AnimatedStep/useAnimatedStepContext.ts index 3edc71e5289e..2adde8fd576e 100644 --- a/src/components/AnimatedStep/useAnimatedStepContext.ts +++ b/src/components/AnimatedStep/useAnimatedStepContext.ts @@ -1,5 +1,6 @@ import {useContext} from 'react'; -import AnimatedStepContext, {StepContext} from './AnimatedStepContext'; +import type {StepContext} from './AnimatedStepContext'; +import AnimatedStepContext from './AnimatedStepContext'; function useAnimatedStepContext(): StepContext { const context = useContext(AnimatedStepContext); diff --git a/src/components/AnonymousReportFooter.tsx b/src/components/AnonymousReportFooter.tsx index 877ca9444661..ad79e316baf3 100644 --- a/src/components/AnonymousReportFooter.tsx +++ b/src/components/AnonymousReportFooter.tsx @@ -1,11 +1,11 @@ import React from 'react'; import {Text, View} from 'react-native'; -import {OnyxCollection} from 'react-native-onyx'; -import {OnyxEntry} from 'react-native-onyx/lib/types'; +import type {OnyxCollection} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx/lib/types'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Session from '@userActions/Session'; -import {PersonalDetails, Report} from '@src/types/onyx'; +import type {PersonalDetails, Report} from '@src/types/onyx'; import AvatarWithDisplayName from './AvatarWithDisplayName'; import Button from './Button'; import ExpensifyWordmark from './ExpensifyWordmark'; diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index 2dae84106971..083c8340baa6 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -1,6 +1,7 @@ import lodashEscape from 'lodash/escape'; import React from 'react'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 51912c04eb31..d24d1e18907f 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -366,7 +366,7 @@ function AttachmentModal(props) { setIsAuthTokenRequired(props.isAuthTokenRequired); }, [props.isAuthTokenRequired]); - const sourceForAttachmentView = props.source || source; + const sourceForAttachmentView = source || props.source; const threeDotsMenuItems = useMemo(() => { if (!props.isReceiptAttachment || !props.parentReport || !props.parentReportActions) { diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx index b72662f989dc..fc3bf4659bd7 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -1,6 +1,7 @@ import {FlashList} from '@shopify/flash-list'; -import React, {ForwardedRef, forwardRef, ReactElement, useCallback, useEffect, useMemo, useRef} from 'react'; -import {View} from 'react-native'; +import type {ForwardedRef, ReactElement} from 'react'; +import React, {forwardRef, useCallback, useEffect, useMemo, useRef} from 'react'; +import type {View} from 'react-native'; // We take ScrollView from this package to properly handle the scrolling of AutoCompleteSuggestions in chats since one scroll is nested inside another import {ScrollView} from 'react-native-gesture-handler'; import Animated, {Easing, FadeOutDown, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; diff --git a/src/components/AutoCompleteSuggestions/types.ts b/src/components/AutoCompleteSuggestions/types.ts index 9130f5139d71..61d614dcf2e4 100644 --- a/src/components/AutoCompleteSuggestions/types.ts +++ b/src/components/AutoCompleteSuggestions/types.ts @@ -1,4 +1,4 @@ -import {ReactElement} from 'react'; +import type {ReactElement} from 'react'; type MeasureParentContainerCallback = (x: number, y: number, width: number) => void; diff --git a/src/components/AutoUpdateTime.tsx b/src/components/AutoUpdateTime.tsx index 258bdb281eb2..f6be32fd3970 100644 --- a/src/components/AutoUpdateTime.tsx +++ b/src/components/AutoUpdateTime.tsx @@ -6,9 +6,10 @@ import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import DateUtils from '@libs/DateUtils'; -import {Timezone} from '@src/types/onyx/PersonalDetails'; +import type {Timezone} from '@src/types/onyx/PersonalDetails'; import Text from './Text'; -import withLocalize, {WithLocalizeProps} from './withLocalize'; +import type {WithLocalizeProps} from './withLocalize'; +import withLocalize from './withLocalize'; type AutoUpdateTimeProps = WithLocalizeProps & { /** Timezone of the user from their personal details */ diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index 65b0b6c36061..4da91c2e7d19 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -1,14 +1,15 @@ import React, {useEffect, useState} from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useNetwork from '@hooks/useNetwork'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportUtils from '@libs/ReportUtils'; -import {AvatarSource} from '@libs/UserUtils'; +import type {AvatarSource} from '@libs/UserUtils'; import type {AvatarSizeName} from '@styles/utils'; import CONST from '@src/CONST'; -import {AvatarType} from '@src/types/onyx/OnyxCommon'; +import type {AvatarType} from '@src/types/onyx/OnyxCommon'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import Image from './Image'; diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index b9bae33d7e23..4580f3b7e4d4 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -1,7 +1,8 @@ import React, {useCallback, useEffect, useRef} from 'react'; import {View} from 'react-native'; -import {OnyxCollection, OnyxEntry, withOnyx} from 'react-native-onyx'; -import {ValueOf} from 'type-fest'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -11,7 +12,7 @@ import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import {PersonalDetails, Policy, Report, ReportActions} from '@src/types/onyx'; +import type {PersonalDetails, Policy, Report, ReportActions} from '@src/types/onyx'; import DisplayNames from './DisplayNames'; import MultipleAvatars from './MultipleAvatars'; import ParentNavigationSubtitle from './ParentNavigationSubtitle'; diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index d766d4cb6f22..71193147c292 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -322,7 +322,7 @@ function AvatarWithImagePicker({ src={Expensicons.Camera} width={variables.iconSizeSmall} height={variables.iconSizeSmall} - fill={theme.textLight} + fill={theme.icon} /> diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index 70aebc30ee83..5be33e6ff2ec 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -1,5 +1,6 @@ import React, {useCallback} from 'react'; -import {GestureResponderEvent, PressableStateCallbackType, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; +import type {GestureResponderEvent, PressableStateCallbackType, StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; diff --git a/src/components/Banner.tsx b/src/components/Banner.tsx index d57257efab49..56fe7c4d0b42 100644 --- a/src/components/Banner.tsx +++ b/src/components/Banner.tsx @@ -1,5 +1,6 @@ import React, {memo} from 'react'; -import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'; +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; diff --git a/src/components/BaseMiniContextMenuItem.tsx b/src/components/BaseMiniContextMenuItem.tsx index 8d115a37cba7..1f9a14cdfdee 100644 --- a/src/components/BaseMiniContextMenuItem.tsx +++ b/src/components/BaseMiniContextMenuItem.tsx @@ -1,5 +1,7 @@ -import React, {ForwardedRef} from 'react'; -import {PressableStateCallbackType, View} from 'react-native'; +import type {ForwardedRef} from 'react'; +import React from 'react'; +import type {PressableStateCallbackType} from 'react-native'; +import {View} from 'react-native'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import DomUtils from '@libs/DomUtils'; diff --git a/src/components/BlockingViews/BlockingView.tsx b/src/components/BlockingViews/BlockingView.tsx index 3a038c58d886..edec30604b88 100644 --- a/src/components/BlockingViews/BlockingView.tsx +++ b/src/components/BlockingViews/BlockingView.tsx @@ -1,6 +1,7 @@ import React from 'react'; -import {ImageSourcePropType, View} from 'react-native'; -import {SvgProps} from 'react-native-svg'; +import type {ImageSourcePropType} from 'react-native'; +import {View} from 'react-native'; +import type {SvgProps} from 'react-native-svg'; import AutoEmailLink from '@components/AutoEmailLink'; import Icon from '@components/Icon'; import Text from '@components/Text'; @@ -9,7 +10,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; -import {TranslationPaths} from '@src/languages/types'; +import type {TranslationPaths} from '@src/languages/types'; type BlockingViewProps = { /** Expensicon for the page */ diff --git a/src/components/BlockingViews/FullPageNotFoundView.tsx b/src/components/BlockingViews/FullPageNotFoundView.tsx index 6d7f838bf6c2..5993e60861f5 100644 --- a/src/components/BlockingViews/FullPageNotFoundView.tsx +++ b/src/components/BlockingViews/FullPageNotFoundView.tsx @@ -6,7 +6,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import variables from '@styles/variables'; -import {TranslationPaths} from '@src/languages/types'; +import type {TranslationPaths} from '@src/languages/types'; import ROUTES from '@src/ROUTES'; import BlockingView from './BlockingView'; diff --git a/src/components/BlockingViews/FullPageOfflineBlockingView.tsx b/src/components/BlockingViews/FullPageOfflineBlockingView.tsx index a9ebcf969ae5..787752dd4e72 100644 --- a/src/components/BlockingViews/FullPageOfflineBlockingView.tsx +++ b/src/components/BlockingViews/FullPageOfflineBlockingView.tsx @@ -3,7 +3,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; -import ChildrenProps from '@src/types/utils/ChildrenProps'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; import BlockingView from './BlockingView'; function FullPageOfflineBlockingView({children}: ChildrenProps) { diff --git a/src/components/Breadcrumbs.tsx b/src/components/Breadcrumbs.tsx index fa761218b4a0..6af3a4c6d477 100644 --- a/src/components/Breadcrumbs.tsx +++ b/src/components/Breadcrumbs.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import LogoComponent from '@assets/images/expensify-wordmark.svg'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index cdafd0b0b93b..dd0499d4d243 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -1,6 +1,8 @@ import {useIsFocused} from '@react-navigation/native'; -import React, {ForwardedRef, useCallback} from 'react'; -import {ActivityIndicator, GestureResponderEvent, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; +import type {ForwardedRef} from 'react'; +import React, {useCallback} from 'react'; +import type {GestureResponderEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {ActivityIndicator, View} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; @@ -12,8 +14,8 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import HapticFeedback from '@libs/HapticFeedback'; import CONST from '@src/CONST'; -import ChildrenProps from '@src/types/utils/ChildrenProps'; -import IconAsset from '@src/types/utils/IconAsset'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; +import type IconAsset from '@src/types/utils/IconAsset'; import validateSubmitShortcut from './validateSubmitShortcut'; type ButtonWithText = { @@ -221,7 +223,7 @@ function Button( @@ -232,7 +234,7 @@ function Button( diff --git a/src/components/Button/validateSubmitShortcut/index.native.ts b/src/components/Button/validateSubmitShortcut/index.native.ts index 7687855f109b..3f277ed208a1 100644 --- a/src/components/Button/validateSubmitShortcut/index.native.ts +++ b/src/components/Button/validateSubmitShortcut/index.native.ts @@ -1,4 +1,4 @@ -import ValidateSubmitShortcut from './types'; +import type ValidateSubmitShortcut from './types'; /** * Validate if the submit shortcut should be triggered depending on the button state diff --git a/src/components/Button/validateSubmitShortcut/index.ts b/src/components/Button/validateSubmitShortcut/index.ts index 55b3e44192e4..695c56af7bb7 100644 --- a/src/components/Button/validateSubmitShortcut/index.ts +++ b/src/components/Button/validateSubmitShortcut/index.ts @@ -1,4 +1,4 @@ -import ValidateSubmitShortcut from './types'; +import type ValidateSubmitShortcut from './types'; /** * Validate if the submit shortcut should be triggered depending on the button state diff --git a/src/components/Button/validateSubmitShortcut/types.ts b/src/components/Button/validateSubmitShortcut/types.ts index 9970e1478a4c..088718d0334e 100644 --- a/src/components/Button/validateSubmitShortcut/types.ts +++ b/src/components/Button/validateSubmitShortcut/types.ts @@ -1,4 +1,4 @@ -import {GestureResponderEvent} from 'react-native'; +import type {GestureResponderEvent} from 'react-native'; type ValidateSubmitShortcut = (isFocused: boolean, isDisabled: boolean, isLoading: boolean, event?: GestureResponderEvent | KeyboardEvent) => boolean; diff --git a/src/components/CardPreview.tsx b/src/components/CardPreview.tsx index 2196e6f441bb..3ac56d6b26a8 100644 --- a/src/components/CardPreview.tsx +++ b/src/components/CardPreview.tsx @@ -1,12 +1,13 @@ import React from 'react'; import {View} from 'react-native'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import ExpensifyCardImage from '@assets/images/expensify-card.svg'; import usePrivatePersonalDetails from '@hooks/usePrivatePersonalDetails'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; -import {PrivatePersonalDetails, Session} from '@src/types/onyx'; +import type {PrivatePersonalDetails, Session} from '@src/types/onyx'; import ImageSVG from './ImageSVG'; import Text from './Text'; diff --git a/src/components/Checkbox.tsx b/src/components/Checkbox.tsx index ac18b550501d..7e7720b57a6e 100644 --- a/src/components/Checkbox.tsx +++ b/src/components/Checkbox.tsx @@ -1,10 +1,12 @@ -import React, {type ForwardedRef, forwardRef, type MouseEventHandler, type KeyboardEvent as ReactKeyboardEvent} from 'react'; -import {GestureResponderEvent, StyleProp, View, ViewStyle} from 'react-native'; +import React, {forwardRef} from 'react'; +import type {ForwardedRef, MouseEventHandler, KeyboardEvent as ReactKeyboardEvent} from 'react'; +import type {GestureResponderEvent, StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; -import ChildrenProps from '@src/types/utils/ChildrenProps'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import PressableWithFeedback from './Pressable/PressableWithFeedback'; diff --git a/src/components/CheckboxWithLabel.tsx b/src/components/CheckboxWithLabel.tsx index 9660c9e1a2e5..a25ccf184f52 100644 --- a/src/components/CheckboxWithLabel.tsx +++ b/src/components/CheckboxWithLabel.tsx @@ -1,5 +1,7 @@ -import React, {ComponentType, ForwardedRef, useState} from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {ComponentType, ForwardedRef} from 'react'; +import React, {useState} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import Checkbox from './Checkbox'; diff --git a/src/components/CollapsibleSection/Collapsible/index.native.tsx b/src/components/CollapsibleSection/Collapsible/index.native.tsx index e8d3dc9439d0..63cb427f845f 100644 --- a/src/components/CollapsibleSection/Collapsible/index.native.tsx +++ b/src/components/CollapsibleSection/Collapsible/index.native.tsx @@ -1,6 +1,6 @@ import React from 'react'; import CollapsibleRN from 'react-native-collapsible'; -import CollapsibleProps from './types'; +import type CollapsibleProps from './types'; function Collapsible({isOpened = false, children}: CollapsibleProps) { return {children}; diff --git a/src/components/CollapsibleSection/Collapsible/index.tsx b/src/components/CollapsibleSection/Collapsible/index.tsx index 2585fd92f42b..5bf31c10ee68 100644 --- a/src/components/CollapsibleSection/Collapsible/index.tsx +++ b/src/components/CollapsibleSection/Collapsible/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {Collapse} from 'react-collapse'; -import CollapsibleProps from './types'; +import type CollapsibleProps from './types'; function Collapsible({isOpened = false, children}: CollapsibleProps) { return {children}; diff --git a/src/components/CollapsibleSection/Collapsible/types.ts b/src/components/CollapsibleSection/Collapsible/types.ts index 8b8e8aba6860..a60b8d79f935 100644 --- a/src/components/CollapsibleSection/Collapsible/types.ts +++ b/src/components/CollapsibleSection/Collapsible/types.ts @@ -1,4 +1,4 @@ -import ChildrenProps from '@src/types/utils/ChildrenProps'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; type CollapsibleProps = ChildrenProps & { /** Whether the section should start expanded. False by default */ diff --git a/src/components/CollapsibleSection/index.tsx b/src/components/CollapsibleSection/index.tsx index 18b24269b773..393a48e85616 100644 --- a/src/components/CollapsibleSection/index.tsx +++ b/src/components/CollapsibleSection/index.tsx @@ -7,7 +7,7 @@ import Text from '@components/Text'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; -import ChildrenProps from '@src/types/utils/ChildrenProps'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; import Collapsible from './Collapsible'; type CollapsibleSectionProps = ChildrenProps & { diff --git a/src/components/ComposeProviders.tsx b/src/components/ComposeProviders.tsx index 2c73719358d8..8fc487c78251 100644 --- a/src/components/ComposeProviders.tsx +++ b/src/components/ComposeProviders.tsx @@ -1,5 +1,6 @@ -import React, {ComponentType, ReactNode} from 'react'; -import ChildrenProps from '@src/types/utils/ChildrenProps'; +import type {ComponentType, ReactNode} from 'react'; +import React from 'react'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; type ComposeProvidersProps = ChildrenProps & { /** Provider components go here */ diff --git a/src/components/Composer/index.android.tsx b/src/components/Composer/index.android.tsx index 46c2a5f06ded..d60a41e0f263 100644 --- a/src/components/Composer/index.android.tsx +++ b/src/components/Composer/index.android.tsx @@ -1,10 +1,12 @@ -import React, {ForwardedRef, useCallback, useEffect, useMemo, useRef} from 'react'; -import {StyleSheet, TextInput} from 'react-native'; +import type {ForwardedRef} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import type {TextInput} from 'react-native'; +import {StyleSheet} from 'react-native'; import RNTextInput from '@components/RNTextInput'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ComposerUtils from '@libs/ComposerUtils'; -import {ComposerProps} from './types'; +import type {ComposerProps} from './types'; function Composer( { diff --git a/src/components/Composer/index.ios.tsx b/src/components/Composer/index.ios.tsx index 240dfabded0b..b1357fef9a46 100644 --- a/src/components/Composer/index.ios.tsx +++ b/src/components/Composer/index.ios.tsx @@ -1,10 +1,12 @@ -import React, {ForwardedRef, useCallback, useEffect, useMemo, useRef} from 'react'; -import {StyleSheet, TextInput} from 'react-native'; +import type {ForwardedRef} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef} from 'react'; +import type {TextInput} from 'react-native'; +import {StyleSheet} from 'react-native'; import RNTextInput from '@components/RNTextInput'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ComposerUtils from '@libs/ComposerUtils'; -import {ComposerProps} from './types'; +import type {ComposerProps} from './types'; function Composer( { diff --git a/src/components/Composer/index.tsx b/src/components/Composer/index.tsx index 4ff5c6dbd75f..19b7bb6bb30a 100755 --- a/src/components/Composer/index.tsx +++ b/src/components/Composer/index.tsx @@ -1,9 +1,11 @@ import {useNavigation} from '@react-navigation/native'; import ExpensiMark from 'expensify-common/lib/ExpensiMark'; -import React, {BaseSyntheticEvent, ForwardedRef, useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import type {BaseSyntheticEvent, ForwardedRef} from 'react'; +import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; import {flushSync} from 'react-dom'; -import {DimensionValue, NativeSyntheticEvent, Text as RNText, StyleSheet, TextInput, TextInputKeyPressEventData, TextInputProps, TextInputSelectionChangeEventData, View} from 'react-native'; -import {AnimatedProps} from 'react-native-reanimated'; +import type {DimensionValue, NativeSyntheticEvent, Text as RNText, TextInput, TextInputKeyPressEventData, TextInputProps, TextInputSelectionChangeEventData} from 'react-native'; +import {StyleSheet, View} from 'react-native'; +import type {AnimatedProps} from 'react-native-reanimated'; import RNTextInput from '@components/RNTextInput'; import Text from '@components/Text'; import useIsScrollBarVisible from '@hooks/useIsScrollBarVisible'; @@ -17,7 +19,7 @@ import updateIsFullComposerAvailable from '@libs/ComposerUtils/updateIsFullCompo import isEnterWhileComposition from '@libs/KeyboardShortcut/isEnterWhileComposition'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; import CONST from '@src/CONST'; -import {ComposerProps} from './types'; +import type {ComposerProps} from './types'; /** * Retrieves the characters from the specified cursor position up to the next space or new line. diff --git a/src/components/Composer/types.ts b/src/components/Composer/types.ts index cc0654b68019..bfdcb6715d40 100644 --- a/src/components/Composer/types.ts +++ b/src/components/Composer/types.ts @@ -1,4 +1,4 @@ -import {NativeSyntheticEvent, StyleProp, TextInputFocusEventData, TextInputKeyPressEventData, TextInputSelectionChangeEventData, TextStyle} from 'react-native'; +import type {NativeSyntheticEvent, StyleProp, TextInputFocusEventData, TextInputKeyPressEventData, TextInputSelectionChangeEventData, TextStyle} from 'react-native'; type TextSelection = { start: number; diff --git a/src/components/ConfirmContent.tsx b/src/components/ConfirmContent.tsx index 9e4ffa8720da..94146a2c2957 100644 --- a/src/components/ConfirmContent.tsx +++ b/src/components/ConfirmContent.tsx @@ -1,11 +1,13 @@ -import React, {ReactNode} from 'react'; -import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'; +import type {ReactNode} from 'react'; +import React from 'react'; +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; -import IconAsset from '@src/types/utils/IconAsset'; +import type IconAsset from '@src/types/utils/IconAsset'; import Button from './Button'; import Header from './Header'; import Icon from './Icon'; diff --git a/src/components/ConfirmPopover.js b/src/components/ConfirmPopover.js deleted file mode 100644 index 83001736b471..000000000000 --- a/src/components/ConfirmPopover.js +++ /dev/null @@ -1,85 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import ConfirmContent from './ConfirmContent'; -import Popover from './Popover'; -import withWindowDimensions, {windowDimensionsPropTypes} from './withWindowDimensions'; - -const propTypes = { - /** Title of the modal */ - title: PropTypes.string.isRequired, - - /** A callback to call when the form has been submitted */ - onConfirm: PropTypes.func.isRequired, - - /** A callback to call when the form has been closed */ - onCancel: PropTypes.func, - - /** Modal visibility */ - isVisible: PropTypes.bool.isRequired, - - /** Confirm button text */ - confirmText: PropTypes.string, - - /** Cancel button text */ - cancelText: PropTypes.string, - - /** Is the action destructive */ - danger: PropTypes.bool, - - /** Whether we should show the cancel button */ - shouldShowCancelButton: PropTypes.bool, - - /** Modal content text/element */ - prompt: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), - - /** Where the popover should be positioned */ - anchorPosition: PropTypes.shape({ - top: PropTypes.number, - left: PropTypes.number, - }).isRequired, - - /** Styles for view */ - // eslint-disable-next-line react/forbid-prop-types - contentStyles: PropTypes.arrayOf(PropTypes.object), - - ...windowDimensionsPropTypes, -}; - -const defaultProps = { - confirmText: '', - cancelText: '', - danger: false, - onCancel: () => {}, - shouldShowCancelButton: true, - prompt: '', - contentStyles: [], -}; - -function ConfirmPopover(props) { - return ( - - - - ); -} - -ConfirmPopover.propTypes = propTypes; -ConfirmPopover.defaultProps = defaultProps; -ConfirmPopover.displayName = 'ConfirmPopover'; -export default withWindowDimensions(ConfirmPopover); diff --git a/src/components/ConfirmationPage.tsx b/src/components/ConfirmationPage.tsx index 21813edd693d..fe4036a4435f 100644 --- a/src/components/ConfirmationPage.tsx +++ b/src/components/ConfirmationPage.tsx @@ -5,7 +5,7 @@ import Button from './Button'; import FixedFooter from './FixedFooter'; import Lottie from './Lottie'; import LottieAnimations from './LottieAnimations'; -import DotLottieAnimation from './LottieAnimations/types'; +import type DotLottieAnimation from './LottieAnimations/types'; import Text from './Text'; type ConfirmationPageProps = { diff --git a/src/components/CopyTextToClipboard.tsx b/src/components/CopyTextToClipboard.tsx index 6f3b42e88fee..3c99a85fd0e7 100644 --- a/src/components/CopyTextToClipboard.tsx +++ b/src/components/CopyTextToClipboard.tsx @@ -1,5 +1,5 @@ import React, {useCallback} from 'react'; -import {AccessibilityRole, StyleProp, TextStyle} from 'react-native'; +import type {AccessibilityRole, StyleProp, TextStyle} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import Clipboard from '@libs/Clipboard'; import * as Expensicons from './Icon/Expensicons'; diff --git a/src/components/CurrencySymbolButton.js b/src/components/CurrencySymbolButton.tsx similarity index 83% rename from src/components/CurrencySymbolButton.js rename to src/components/CurrencySymbolButton.tsx index d03834fc1fd6..18955bb0b391 100644 --- a/src/components/CurrencySymbolButton.js +++ b/src/components/CurrencySymbolButton.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React from 'react'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -7,15 +6,15 @@ import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import Text from './Text'; import Tooltip from './Tooltip'; -const propTypes = { +type CurrencySymbolButtonProps = { /** Currency symbol of selected currency */ - currencySymbol: PropTypes.string.isRequired, + currencySymbol: string; /** Function to call when currency button is pressed */ - onCurrencyButtonPress: PropTypes.func.isRequired, + onCurrencyButtonPress: () => void; }; -function CurrencySymbolButton({onCurrencyButtonPress, currencySymbol}) { +function CurrencySymbolButton({onCurrencyButtonPress, currencySymbol}: CurrencySymbolButtonProps) { const styles = useThemeStyles(); const {translate} = useLocalize(); return ( @@ -31,7 +30,6 @@ function CurrencySymbolButton({onCurrencyButtonPress, currencySymbol}) { ); } -CurrencySymbolButton.propTypes = propTypes; CurrencySymbolButton.displayName = 'CurrencySymbolButton'; export default CurrencySymbolButton; diff --git a/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx b/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx index 02c308705994..367e54e8be64 100644 --- a/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx +++ b/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {View} from 'react-native'; import {Circle, Rect} from 'react-native-svg'; -import {ValueOf} from 'type-fest'; +import type {ValueOf} from 'type-fest'; import SkeletonViewContentLoader from '@components/SkeletonViewContentLoader'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; diff --git a/src/components/CurrentWalletBalance.tsx b/src/components/CurrentWalletBalance.tsx index 28a83fb1ae50..8761b50465f3 100644 --- a/src/components/CurrentWalletBalance.tsx +++ b/src/components/CurrentWalletBalance.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import {StyleProp, TextStyle} from 'react-native'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import type {StyleProp, TextStyle} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import ONYXKEYS from '@src/ONYXKEYS'; -import UserWallet from '@src/types/onyx/UserWallet'; +import type UserWallet from '@src/types/onyx/UserWallet'; import Text from './Text'; type CurrentWalletBalanceOnyxProps = { diff --git a/src/components/CustomDevMenu/index.native.tsx b/src/components/CustomDevMenu/index.native.tsx index d8a0ea987171..54f1336b4fef 100644 --- a/src/components/CustomDevMenu/index.native.tsx +++ b/src/components/CustomDevMenu/index.native.tsx @@ -1,7 +1,7 @@ import {useEffect} from 'react'; import DevMenu from 'react-native-dev-menu'; import toggleTestToolsModal from '@userActions/TestTool'; -import CustomDevMenuElement from './types'; +import type CustomDevMenuElement from './types'; const CustomDevMenu: CustomDevMenuElement = Object.assign( () => { diff --git a/src/components/CustomDevMenu/index.tsx b/src/components/CustomDevMenu/index.tsx index c8eae861b676..4306d0cae090 100644 --- a/src/components/CustomDevMenu/index.tsx +++ b/src/components/CustomDevMenu/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import CustomDevMenuElement from './types'; +import type CustomDevMenuElement from './types'; const CustomDevMenu: CustomDevMenuElement = Object.assign(() => <>, {displayName: 'CustomDevMenu'}); diff --git a/src/components/CustomDevMenu/types.ts b/src/components/CustomDevMenu/types.ts index bdfc800a17f0..17e2b30ef4f6 100644 --- a/src/components/CustomDevMenu/types.ts +++ b/src/components/CustomDevMenu/types.ts @@ -1,4 +1,4 @@ -import {ReactElement} from 'react'; +import type {ReactElement} from 'react'; type CustomDevMenuElement = { (): ReactElement; diff --git a/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/index.website.ts b/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/index.website.ts index 481d866dbe4f..f92016048bde 100644 --- a/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/index.website.ts +++ b/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/index.website.ts @@ -1,4 +1,4 @@ -import UpdateGlobalBackgroundColor from './types'; +import type UpdateGlobalBackgroundColor from './types'; const updateGlobalBackgroundColor: UpdateGlobalBackgroundColor = (theme) => { const htmlElement = document.getElementsByTagName('html')[0]; diff --git a/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/types.ts b/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/types.ts index 83bd36a9428a..078703e3846d 100644 --- a/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/types.ts +++ b/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/types.ts @@ -1,4 +1,4 @@ -import {ThemeColors} from '@styles/theme/types'; +import type {ThemeColors} from '@styles/theme/types'; type UpdateGlobalBackgroundColor = (theme: ThemeColors) => void; diff --git a/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.android.ts b/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.android.ts index b7651d4549de..c25f45bfb050 100644 --- a/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.android.ts +++ b/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.android.ts @@ -1,5 +1,5 @@ import StatusBar from '@libs/StatusBar'; -import UpdateStatusBarAppearanceProps from './types'; +import type UpdateStatusBarAppearanceProps from './types'; // eslint-disable-next-line @typescript-eslint/naming-convention export default function updateStatusBarAppearance({statusBarStyle}: UpdateStatusBarAppearanceProps) { diff --git a/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.ios.ts b/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.ios.ts index 61fcb056bba5..e887b5f0a98b 100644 --- a/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.ios.ts +++ b/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.ios.ts @@ -1,5 +1,5 @@ import StatusBar from '@libs/StatusBar'; -import UpdateStatusBarAppearanceProps from './types'; +import type UpdateStatusBarAppearanceProps from './types'; // eslint-disable-next-line @typescript-eslint/naming-convention export default function updateStatusBarAppearance({statusBarStyle}: UpdateStatusBarAppearanceProps) { diff --git a/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.ts b/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.ts index 574efd90f0b5..a11b3619f8d9 100644 --- a/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.ts +++ b/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.ts @@ -1,5 +1,5 @@ import StatusBar from '@libs/StatusBar'; -import UpdateStatusBarAppearanceProps from './types'; +import type UpdateStatusBarAppearanceProps from './types'; export default function updateStatusBarAppearance({backgroundColor, statusBarStyle}: UpdateStatusBarAppearanceProps) { if (backgroundColor) { diff --git a/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/types.ts b/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/types.ts index 823f0059eccf..775082187395 100644 --- a/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/types.ts +++ b/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/types.ts @@ -1,4 +1,4 @@ -import {StatusBarStyle} from '@styles/index'; +import type {StatusBarStyle} from '@styles/index'; type UpdateStatusBarAppearanceProps = { backgroundColor?: string; diff --git a/src/components/DeeplinkWrapper/DeeplinkRedirectLoadingIndicator.tsx b/src/components/DeeplinkWrapper/DeeplinkRedirectLoadingIndicator.tsx index 0e8996bf5022..27d8027bbcff 100644 --- a/src/components/DeeplinkWrapper/DeeplinkRedirectLoadingIndicator.tsx +++ b/src/components/DeeplinkWrapper/DeeplinkRedirectLoadingIndicator.tsx @@ -1,6 +1,7 @@ import React from 'react'; import {View} from 'react-native'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import * as Illustrations from '@components/Icon/Illustrations'; @@ -12,7 +13,7 @@ import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; -import * as OnyxTypes from '@src/types/onyx'; +import type * as OnyxTypes from '@src/types/onyx'; type DeeplinkRedirectLoadingIndicatorOnyxProps = { /** Current user session */ diff --git a/src/components/DeeplinkWrapper/index.tsx b/src/components/DeeplinkWrapper/index.tsx index 4b0382bd6b14..076e6b91460d 100644 --- a/src/components/DeeplinkWrapper/index.tsx +++ b/src/components/DeeplinkWrapper/index.tsx @@ -1,4 +1,4 @@ -import DeeplinkWrapperProps from './types'; +import type DeeplinkWrapperProps from './types'; function DeeplinkWrapper({children}: DeeplinkWrapperProps) { return children; diff --git a/src/components/DeeplinkWrapper/index.website.tsx b/src/components/DeeplinkWrapper/index.website.tsx index 2cae91e2f2a0..1d509666ec98 100644 --- a/src/components/DeeplinkWrapper/index.website.tsx +++ b/src/components/DeeplinkWrapper/index.website.tsx @@ -8,7 +8,7 @@ import * as App from '@userActions/App'; import CONFIG from '@src/CONFIG'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; -import DeeplinkWrapperProps from './types'; +import type DeeplinkWrapperProps from './types'; function isMacOSWeb(): boolean { return !Browser.isMobile() && typeof navigator === 'object' && typeof navigator.userAgent === 'string' && /Mac/i.test(navigator.userAgent) && !/Electron/i.test(navigator.userAgent); diff --git a/src/components/DeeplinkWrapper/types.ts b/src/components/DeeplinkWrapper/types.ts index dfd56b62573d..db61e5b01c24 100644 --- a/src/components/DeeplinkWrapper/types.ts +++ b/src/components/DeeplinkWrapper/types.ts @@ -1,4 +1,4 @@ -import ChildrenProps from '@src/types/utils/ChildrenProps'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; type DeeplinkWrapperProps = ChildrenProps & { /** User authentication status */ diff --git a/src/components/DisplayNames/DisplayNamesTooltipItem.tsx b/src/components/DisplayNames/DisplayNamesTooltipItem.tsx index 440457d22965..9b3eaeb4e447 100644 --- a/src/components/DisplayNames/DisplayNamesTooltipItem.tsx +++ b/src/components/DisplayNames/DisplayNamesTooltipItem.tsx @@ -1,15 +1,16 @@ -import React, {RefObject, useCallback} from 'react'; -import {Text as RNText, StyleProp, TextStyle} from 'react-native'; +import type {RefObject} from 'react'; +import React, {useCallback} from 'react'; +import type {Text as RNText, StyleProp, TextStyle} from 'react-native'; import Text from '@components/Text'; import UserDetailsTooltip from '@components/UserDetailsTooltip'; import useThemeStyles from '@hooks/useThemeStyles'; -import {AvatarSource} from '@libs/UserUtils'; +import type {AvatarSource} from '@libs/UserUtils'; type DisplayNamesTooltipItemProps = { index?: number; /** The function to get a distance to shift the tooltip horizontally */ - getTooltipShiftX?: (index: number) => number | undefined; + getTooltipShiftX?: (index: number) => number; /** The Account ID for the tooltip */ accountID?: number; @@ -32,7 +33,7 @@ type DisplayNamesTooltipItemProps = { function DisplayNamesTooltipItem({ index = 0, - getTooltipShiftX = () => undefined, + getTooltipShiftX = () => 0, accountID = 0, avatar = '', login = '', diff --git a/src/components/DisplayNames/DisplayNamesWithTooltip.tsx b/src/components/DisplayNames/DisplayNamesWithTooltip.tsx index 43061ada9a94..f22b1f0c2209 100644 --- a/src/components/DisplayNames/DisplayNamesWithTooltip.tsx +++ b/src/components/DisplayNames/DisplayNamesWithTooltip.tsx @@ -1,10 +1,11 @@ import React, {Fragment, useCallback, useRef} from 'react'; -import {Text as RNText, View} from 'react-native'; +import type {Text as RNText} from 'react-native'; +import {View} from 'react-native'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; import useThemeStyles from '@hooks/useThemeStyles'; import DisplayNamesTooltipItem from './DisplayNamesTooltipItem'; -import DisplayNamesProps from './types'; +import type DisplayNamesProps from './types'; type HTMLElementWithText = HTMLElement & RNText; @@ -23,13 +24,13 @@ function DisplayNamesWithToolTip({shouldUseFullTitle, fullTitle, displayNamesWit * 2. Now we get the tooltip original position. * 3. If inline node's right edge is overflowing the container's right edge, we set the tooltip to the center * of the distance between the left edge of the inline node and right edge of the container. - * @param {Number} index Used to get the Ref to the node at the current index - * @returns {Number} Distance to shift the tooltip horizontally + * @param index Used to get the Ref to the node at the current index + * @returns Distance to shift the tooltip horizontally */ const getTooltipShiftX = useCallback((index: number) => { // Only shift the tooltip in case the containerLayout or Refs to the text node are available if (!containerRef.current || !childRefs.current[index]) { - return; + return 0; } const {width: containerWidth, left: containerLeft} = containerRef.current.getBoundingClientRect(); diff --git a/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx b/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx index 761b0b66ee2c..177bdb6a9fc4 100644 --- a/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx +++ b/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {StyleProp, TextStyle} from 'react-native'; +import type {StyleProp, TextStyle} from 'react-native'; import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/components/DisplayNames/index.native.tsx b/src/components/DisplayNames/index.native.tsx index 8f1fef37a6ba..b3eceb794bcb 100644 --- a/src/components/DisplayNames/index.native.tsx +++ b/src/components/DisplayNames/index.native.tsx @@ -1,7 +1,7 @@ import React from 'react'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; -import DisplayNamesProps from './types'; +import type DisplayNamesProps from './types'; // As we don't have to show tooltips of the Native platform so we simply render the full display names list. function DisplayNames({accessibilityLabel, fullTitle, textStyles = [], numberOfLines = 1}: DisplayNamesProps) { diff --git a/src/components/DisplayNames/index.tsx b/src/components/DisplayNames/index.tsx index 7ff1081937d5..155193368cc5 100644 --- a/src/components/DisplayNames/index.tsx +++ b/src/components/DisplayNames/index.tsx @@ -2,7 +2,7 @@ import React from 'react'; import useLocalize from '@hooks/useLocalize'; import DisplayNamesWithoutTooltip from './DisplayNamesWithoutTooltip'; import DisplayNamesWithToolTip from './DisplayNamesWithTooltip'; -import DisplayNamesProps from './types'; +import type DisplayNamesProps from './types'; function DisplayNames({fullTitle, tooltipEnabled, textStyles, numberOfLines, shouldUseFullTitle, displayNamesWithTooltips}: DisplayNamesProps) { const {translate} = useLocalize(); diff --git a/src/components/DisplayNames/types.ts b/src/components/DisplayNames/types.ts index 5137d6f54108..b0959d43aa96 100644 --- a/src/components/DisplayNames/types.ts +++ b/src/components/DisplayNames/types.ts @@ -1,4 +1,4 @@ -import {StyleProp, TextStyle} from 'react-native'; +import type {StyleProp, TextStyle} from 'react-native'; import type {AvatarSource} from '@libs/UserUtils'; type DisplayNameWithTooltip = { diff --git a/src/components/DistanceMapView/index.android.tsx b/src/components/DistanceMapView/index.android.tsx index 38e92163c3eb..168a480c6100 100644 --- a/src/components/DistanceMapView/index.android.tsx +++ b/src/components/DistanceMapView/index.android.tsx @@ -6,7 +6,7 @@ import MapView from '@components/MapView'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; -import DistanceMapViewProps from './types'; +import type DistanceMapViewProps from './types'; function DistanceMapView({overlayStyle, ...rest}: DistanceMapViewProps) { const styles = useThemeStyles(); diff --git a/src/components/DistanceMapView/index.tsx b/src/components/DistanceMapView/index.tsx index 2abdc29865b0..3f1b3b8439ed 100644 --- a/src/components/DistanceMapView/index.tsx +++ b/src/components/DistanceMapView/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import MapView from '@components/MapView'; -import DistanceMapViewProps from './types'; +import type DistanceMapViewProps from './types'; function DistanceMapView({overlayStyle, ...rest}: DistanceMapViewProps) { // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/src/components/DistanceMapView/types.ts b/src/components/DistanceMapView/types.ts index 5ab3dbd8238e..18213235445f 100644 --- a/src/components/DistanceMapView/types.ts +++ b/src/components/DistanceMapView/types.ts @@ -1,4 +1,4 @@ -import {StyleProp, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; import type {MapViewProps} from '@components/MapView/MapViewTypes'; type DistanceMapViewProps = MapViewProps & { diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx index 65afe8c7e4eb..d18704fdfb05 100644 --- a/src/components/DotIndicatorMessage.tsx +++ b/src/components/DotIndicatorMessage.tsx @@ -1,6 +1,7 @@ /* eslint-disable react/no-array-index-key */ import React from 'react'; -import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'; +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -84,9 +85,9 @@ function DotIndicatorMessage({messages = {}, style, type, textStyles}: DotIndica key={i} style={styles.offlineFeedback.text} > - {Localize.translateLocal('iou.error.receiptFailureMessage')} - {Localize.translateLocal('iou.error.saveFileMessage')} - {Localize.translateLocal('iou.error.loseFileMessage')} + {Localize.translateLocal('iou.error.receiptFailureMessage')} + {Localize.translateLocal('iou.error.saveFileMessage')} + {Localize.translateLocal('iou.error.loseFileMessage')} ) : ( diff --git a/src/components/DragAndDrop/Consumer/types.ts b/src/components/DragAndDrop/Consumer/types.ts index 1f85f32a0153..03bdee3f23d8 100644 --- a/src/components/DragAndDrop/Consumer/types.ts +++ b/src/components/DragAndDrop/Consumer/types.ts @@ -1,4 +1,4 @@ -import {ReactNode} from 'react'; +import type {ReactNode} from 'react'; type DragAndDropConsumerProps = { /** Children to render inside this component. */ diff --git a/src/components/DragAndDrop/NoDropZone/types.ts b/src/components/DragAndDrop/NoDropZone/types.ts index 09715ecd942f..0735f4936ada 100644 --- a/src/components/DragAndDrop/NoDropZone/types.ts +++ b/src/components/DragAndDrop/NoDropZone/types.ts @@ -1,4 +1,4 @@ -import {ReactNode} from 'react'; +import type {ReactNode} from 'react'; type NoDropZoneProps = { /** Content */ diff --git a/src/components/DragAndDrop/Provider/types.ts b/src/components/DragAndDrop/Provider/types.ts index eae83d10682a..b4394056cac5 100644 --- a/src/components/DragAndDrop/Provider/types.ts +++ b/src/components/DragAndDrop/Provider/types.ts @@ -1,4 +1,4 @@ -import {ReactNode} from 'react'; +import type {ReactNode} from 'react'; type DragAndDropProviderProps = { /** Children to render inside this component. */ diff --git a/src/components/DraggableList/index.native.tsx b/src/components/DraggableList/index.native.tsx index f532b21720da..7cf33112bece 100644 --- a/src/components/DraggableList/index.native.tsx +++ b/src/components/DraggableList/index.native.tsx @@ -1,6 +1,6 @@ import React from 'react'; import DraggableFlatList from 'react-native-draggable-flatlist'; -import {FlatList} from 'react-native-gesture-handler'; +import type {FlatList} from 'react-native-gesture-handler'; import useThemeStyles from '@hooks/useThemeStyles'; import type {DraggableListProps} from './types'; diff --git a/src/components/DraggableList/index.tsx b/src/components/DraggableList/index.tsx index b92691075424..dc78a3ce6222 100644 --- a/src/components/DraggableList/index.tsx +++ b/src/components/DraggableList/index.tsx @@ -1,5 +1,6 @@ import React, {useCallback} from 'react'; -import {DragDropContext, Draggable, Droppable, type OnDragEndResponder} from 'react-beautiful-dnd'; +import {DragDropContext, Draggable, Droppable} from 'react-beautiful-dnd'; +import type {OnDragEndResponder} from 'react-beautiful-dnd'; import {ScrollView} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import type {DraggableListProps} from './types'; diff --git a/src/components/EmojiSuggestions.tsx b/src/components/EmojiSuggestions.tsx index 2fcf8e827b4e..1c0306741048 100644 --- a/src/components/EmojiSuggestions.tsx +++ b/src/components/EmojiSuggestions.tsx @@ -1,8 +1,9 @@ -import React, {ReactElement, useCallback} from 'react'; +import type {ReactElement} from 'react'; +import React, {useCallback} from 'react'; import {View} from 'react-native'; +import type {Emoji} from '@assets/emojis/types'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import type {SimpleEmoji} from '@libs/EmojiTrie'; import * as EmojiUtils from '@libs/EmojiUtils'; import getStyledTextArray from '@libs/GetStyledTextArray'; import AutoCompleteSuggestions from './AutoCompleteSuggestions'; @@ -15,7 +16,7 @@ type EmojiSuggestionsProps = { highlightedEmojiIndex?: number; /** Array of suggested emoji */ - emojis: SimpleEmoji[]; + emojis: Emoji[]; /** Fired when the user selects an emoji */ onSelect: (index: number) => void; @@ -39,7 +40,7 @@ type EmojiSuggestionsProps = { /** * Create unique keys for each emoji item */ -const keyExtractor = (item: SimpleEmoji, index: number): string => `${item.name}+${index}}`; +const keyExtractor = (item: Emoji, index: number): string => `${item.name}+${index}}`; function EmojiSuggestions({emojis, onSelect, prefix, isEmojiPickerLarge, preferredSkinToneIndex, highlightedEmojiIndex = 0, measureParentContainer = () => {}}: EmojiSuggestionsProps) { const styles = useThemeStyles(); @@ -48,7 +49,7 @@ function EmojiSuggestions({emojis, onSelect, prefix, isEmojiPickerLarge, preferr * Render an emoji suggestion menu item component. */ const renderSuggestionMenuItem = useCallback( - (item: SimpleEmoji): ReactElement => { + (item: Emoji): ReactElement => { const styledTextArray = getStyledTextArray(item.name, prefix); return ( diff --git a/src/components/ErrorBoundary/BaseErrorBoundary.tsx b/src/components/ErrorBoundary/BaseErrorBoundary.tsx index 2a6524d5a993..6a0f1a0ae55e 100644 --- a/src/components/ErrorBoundary/BaseErrorBoundary.tsx +++ b/src/components/ErrorBoundary/BaseErrorBoundary.tsx @@ -2,7 +2,7 @@ import React from 'react'; import {ErrorBoundary} from 'react-error-boundary'; import BootSplash from '@libs/BootSplash'; import GenericErrorPage from '@pages/ErrorPage/GenericErrorPage'; -import {BaseErrorBoundaryProps, LogError} from './types'; +import type {BaseErrorBoundaryProps, LogError} from './types'; /** * This component captures an error in the child component tree and logs it to the server diff --git a/src/components/ErrorBoundary/index.native.tsx b/src/components/ErrorBoundary/index.native.tsx index b8e56ee528c2..5e0c9ca9af38 100644 --- a/src/components/ErrorBoundary/index.native.tsx +++ b/src/components/ErrorBoundary/index.native.tsx @@ -2,7 +2,7 @@ import crashlytics from '@react-native-firebase/crashlytics'; import React from 'react'; import Log from '@libs/Log'; import BaseErrorBoundary from './BaseErrorBoundary'; -import {BaseErrorBoundaryProps, LogError} from './types'; +import type {BaseErrorBoundaryProps, LogError} from './types'; const logError: LogError = (errorMessage, error, errorInfo) => { // Log the error to the server diff --git a/src/components/ErrorBoundary/index.tsx b/src/components/ErrorBoundary/index.tsx index fce70674dd97..890cf5f4e587 100644 --- a/src/components/ErrorBoundary/index.tsx +++ b/src/components/ErrorBoundary/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import Log from '@libs//Log'; import BaseErrorBoundary from './BaseErrorBoundary'; -import {BaseErrorBoundaryProps, LogError} from './types'; +import type {BaseErrorBoundaryProps, LogError} from './types'; const logError: LogError = (errorMessage, error, errorInfo) => { // Log the error to the server diff --git a/src/components/ExpensifyWordmark.tsx b/src/components/ExpensifyWordmark.tsx index 798907c7eeb6..0e8f78686b07 100644 --- a/src/components/ExpensifyWordmark.tsx +++ b/src/components/ExpensifyWordmark.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import AdHocLogo from '@assets/images/expensify-logo--adhoc.svg'; import DevLogo from '@assets/images/expensify-logo--dev.svg'; import StagingLogo from '@assets/images/expensify-logo--staging.svg'; diff --git a/src/components/FixedFooter.tsx b/src/components/FixedFooter.tsx index 7fd6811c1df6..35fa4d02f5e0 100644 --- a/src/components/FixedFooter.tsx +++ b/src/components/FixedFooter.tsx @@ -1,5 +1,7 @@ -import React, {ReactNode} from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {ReactNode} from 'react'; +import React from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; type FixedFooterProps = { diff --git a/src/components/FlatList/index.android.tsx b/src/components/FlatList/index.android.tsx index 84345f6e0ed4..863930203863 100644 --- a/src/components/FlatList/index.android.tsx +++ b/src/components/FlatList/index.android.tsx @@ -1,6 +1,8 @@ import {useFocusEffect} from '@react-navigation/native'; -import React, {ForwardedRef, forwardRef, useCallback, useContext} from 'react'; -import {FlatList, FlatListProps} from 'react-native'; +import type {ForwardedRef} from 'react'; +import React, {forwardRef, useCallback, useContext} from 'react'; +import type {FlatListProps} from 'react-native'; +import {FlatList} from 'react-native'; import {ActionListContext} from '@pages/home/ReportScreenContext'; // FlatList wrapped with the freeze component will lose its scroll state when frozen (only for Android). diff --git a/src/components/FormAlertWithSubmitButton.tsx b/src/components/FormAlertWithSubmitButton.tsx index d8e30b27371d..512d2063dc0f 100644 --- a/src/components/FormAlertWithSubmitButton.tsx +++ b/src/components/FormAlertWithSubmitButton.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import Button from './Button'; import FormAlertWrapper from './FormAlertWrapper'; diff --git a/src/components/FormAlertWrapper.tsx b/src/components/FormAlertWrapper.tsx index a144bf069502..65fa2311620d 100644 --- a/src/components/FormAlertWrapper.tsx +++ b/src/components/FormAlertWrapper.tsx @@ -1,8 +1,10 @@ -import React, {ReactNode} from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {ReactNode} from 'react'; +import React from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; -import Network from '@src/types/onyx/Network'; +import type Network from '@src/types/onyx/Network'; import FormHelpMessage from './FormHelpMessage'; import {withNetwork} from './OnyxProvider'; import RenderHTML from './RenderHTML'; diff --git a/src/components/FormElement.tsx b/src/components/FormElement.tsx index c61a09b9d1ec..da98d4dc565a 100644 --- a/src/components/FormElement.tsx +++ b/src/components/FormElement.tsx @@ -1,5 +1,7 @@ -import React, {ForwardedRef, forwardRef} from 'react'; -import {View, ViewProps} from 'react-native'; +import type {ForwardedRef} from 'react'; +import React, {forwardRef} from 'react'; +import type {ViewProps} from 'react-native'; +import {View} from 'react-native'; import * as ComponentUtils from '@libs/ComponentUtils'; function FormElement(props: ViewProps, ref: ForwardedRef) { diff --git a/src/components/FormHelpMessage.tsx b/src/components/FormHelpMessage.tsx index 43709b51db44..4f1d784788bf 100644 --- a/src/components/FormHelpMessage.tsx +++ b/src/components/FormHelpMessage.tsx @@ -1,6 +1,7 @@ import isEmpty from 'lodash/isEmpty'; import React from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as Localize from '@libs/Localize'; diff --git a/src/components/FormScrollView.tsx b/src/components/FormScrollView.tsx index 4646a534e750..ade167e9e628 100644 --- a/src/components/FormScrollView.tsx +++ b/src/components/FormScrollView.tsx @@ -1,5 +1,7 @@ -import React, {ForwardedRef} from 'react'; -import {ScrollView, ScrollViewProps} from 'react-native'; +import type {ForwardedRef} from 'react'; +import React from 'react'; +import type {ScrollViewProps} from 'react-native'; +import {ScrollView} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; type FormScrollViewProps = ScrollViewProps & { diff --git a/src/components/FormSubmit/index.native.tsx b/src/components/FormSubmit/index.native.tsx index 22bf5353949d..5eae7b51d988 100644 --- a/src/components/FormSubmit/index.native.tsx +++ b/src/components/FormSubmit/index.native.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {View} from 'react-native'; -import {FormSubmitProps, FormSubmitRef} from './types'; +import type {FormSubmitProps, FormSubmitRef} from './types'; function FormSubmit({style, children}: FormSubmitProps, ref: FormSubmitRef) { return ( diff --git a/src/components/FormSubmit/index.tsx b/src/components/FormSubmit/index.tsx index ef3f3c39bbaa..2ccd006bf322 100644 --- a/src/components/FormSubmit/index.tsx +++ b/src/components/FormSubmit/index.tsx @@ -1,9 +1,10 @@ -import React, {KeyboardEvent, useEffect} from 'react'; +import type {KeyboardEvent} from 'react'; +import React, {useEffect} from 'react'; import {View} from 'react-native'; import * as ComponentUtils from '@libs/ComponentUtils'; import isEnterWhileComposition from '@libs/KeyboardShortcut/isEnterWhileComposition'; import CONST from '@src/CONST'; -import {FormSubmitProps, FormSubmitRef} from './types'; +import type {FormSubmitProps, FormSubmitRef} from './types'; function FormSubmit({children, onSubmit, style}: FormSubmitProps, ref: FormSubmitRef) { /** diff --git a/src/components/FormSubmit/types.ts b/src/components/FormSubmit/types.ts index 20910647ecb8..722a3fbf746e 100644 --- a/src/components/FormSubmit/types.ts +++ b/src/components/FormSubmit/types.ts @@ -1,5 +1,6 @@ -import React, {ForwardedRef} from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {ForwardedRef} from 'react'; +import type React from 'react'; +import type {StyleProp, View, ViewStyle} from 'react-native'; type FormSubmitProps = { children: React.ReactNode; diff --git a/src/components/FullscreenLoadingIndicator.tsx b/src/components/FullscreenLoadingIndicator.tsx index 2b5f7b3ada72..bd3082db5fa4 100644 --- a/src/components/FullscreenLoadingIndicator.tsx +++ b/src/components/FullscreenLoadingIndicator.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import {ActivityIndicator, StyleProp, StyleSheet, View, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {ActivityIndicator, StyleSheet, View} from 'react-native'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js index 0b5cbad29983..46d04ca9404d 100755 --- a/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js +++ b/src/components/HTMLEngineProvider/BaseHTMLEngineProvider.js @@ -4,7 +4,7 @@ import {defaultHTMLElementModels, RenderHTMLConfigProvider, TRenderEngineProvide import _ from 'underscore'; import useThemeStyles from '@hooks/useThemeStyles'; import convertToLTR from '@libs/convertToLTR'; -import singleFontFamily from '@styles/utils/fontFamily/singleFontFamily'; +import FontUtils from '@styles/utils/FontUtils'; import * as HTMLEngineUtils from './htmlEngineUtils'; import htmlRenderers from './HTMLRenderers'; @@ -62,7 +62,7 @@ function BaseHTMLEngineProvider(props) { 'mention-here': defaultHTMLElementModels.span.extend({tagName: 'mention-here'}), 'next-step': defaultHTMLElementModels.span.extend({ tagName: 'next-step', - mixedUAStyles: {...styles.textLabelSupporting}, + mixedUAStyles: {...styles.textLabelSupporting, ...styles.lh16}, }), 'next-step-email': defaultHTMLElementModels.span.extend({tagName: 'next-step-email'}), video: defaultHTMLElementModels.div.extend({ @@ -70,7 +70,7 @@ function BaseHTMLEngineProvider(props) { mixedUAStyles: {whiteSpace: 'pre'}, }), }), - [styles.colorMuted, styles.formError, styles.mb0, styles.textLabelSupporting], + [styles.colorMuted, styles.formError, styles.mb0, styles.textLabelSupporting, styles.lh16], ); // We need to memoize this prop to make it referentially stable. @@ -82,7 +82,7 @@ function BaseHTMLEngineProvider(props) { baseStyle={styles.webViewStyles.baseFontStyle} tagsStyles={styles.webViewStyles.tagStyles} enableCSSInlineProcessing={false} - systemFonts={_.values(singleFontFamily)} + systemFonts={_.values(FontUtils.fontFamily.single)} domVisitors={{ // eslint-disable-next-line no-param-reassign onText: (text) => (text.data = convertToLTR(text.data)), diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/BasePreRenderer.js b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.js similarity index 76% rename from src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/BasePreRenderer.js rename to src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.js index 07954cc97a00..27eff02d63ea 100644 --- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/BasePreRenderer.js +++ b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer.js @@ -1,14 +1,14 @@ import PropTypes from 'prop-types'; -import React, {forwardRef} from 'react'; -import {ScrollView, View} from 'react-native'; +import React from 'react'; +import {View} from 'react-native'; import _ from 'underscore'; -import htmlRendererPropTypes from '@components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import {ShowContextMenuContext, showContextMenuForReport} from '@components/ShowContextMenuContext'; import withLocalize from '@components/withLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportUtils from '@libs/ReportUtils'; import CONST from '@src/CONST'; +import htmlRendererPropTypes from './htmlRendererPropTypes'; const propTypes = { /** Press in handler for the code block */ @@ -31,20 +31,14 @@ const defaultProps = { onPressOut: undefined, }; -const BasePreRenderer = forwardRef((props, ref) => { +function PreRenderer(props) { const styles = useThemeStyles(); const TDefaultRenderer = props.TDefaultRenderer; const defaultRendererProps = _.omit(props, ['TDefaultRenderer', 'onPressIn', 'onPressOut', 'onLongPress']); const isLast = props.renderIndex === props.renderLength - 1; return ( - + {({anchor, report, action, checkIfContextMenuActive}) => ( { )} - + ); -}); +} -BasePreRenderer.displayName = 'BasePreRenderer'; -BasePreRenderer.propTypes = propTypes; -BasePreRenderer.defaultProps = defaultProps; +PreRenderer.displayName = 'PreRenderer'; +PreRenderer.propTypes = propTypes; +PreRenderer.defaultProps = defaultProps; -export default withLocalize(BasePreRenderer); +export default withLocalize(PreRenderer); diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js deleted file mode 100644 index 3beb52e6ee81..000000000000 --- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.js +++ /dev/null @@ -1,68 +0,0 @@ -import React, {useCallback, useEffect, useRef} from 'react'; -import _ from 'underscore'; -import htmlRendererPropTypes from '@components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes'; -import ControlSelection from '@libs/ControlSelection'; -import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import BasePreRenderer from './BasePreRenderer'; - -const supportsPassive = DeviceCapabilities.hasPassiveEventListenerSupport(); - -const isScrollingVertically = (event) => - // Mark as vertical scrolling only when absolute value of deltaY is more than the double of absolute - // value of deltaX, so user can use trackpad scroll on the code block horizontally at a wide angle. - Math.abs(event.deltaY) > Math.abs(event.deltaX) * 2; - -const debouncedIsScrollingVertically = _.debounce(isScrollingVertically, 100, true); - -function PreRenderer(props) { - const scrollViewRef = useRef(); - - /** - * Checks if user is scrolling vertically based on deltaX and deltaY. We debounce this - * method in order to make sure it's called only for the first event. - * @param {WheelEvent} event Wheel event - * @returns {Boolean} true if user is scrolling vertically - */ - - /** - * Manually scrolls the code block if code block horizontal scrollable, then prevents the event from being passed up to the parent. - * @param {Object} event native event - */ - const scrollNode = useCallback((event) => { - const node = scrollViewRef.current.getScrollableNode(); - const horizontalOverflow = node.scrollWidth > node.offsetWidth; - if (event.currentTarget === node && horizontalOverflow && !debouncedIsScrollingVertically(event)) { - node.scrollLeft += event.deltaX; - } - }, []); - - useEffect(() => { - const eventListenerRefValue = scrollViewRef.current; - if (!eventListenerRefValue) { - return; - } - eventListenerRefValue.getScrollableNode().addEventListener('wheel', scrollNode, supportsPassive ? {passive: true} : false); - - return () => { - if (!eventListenerRefValue.getScrollableNode()) { - return; - } - eventListenerRefValue.getScrollableNode().removeEventListener('wheel', scrollNode); - }; - }, [scrollNode]); - - return ( - DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} - onPressOut={ControlSelection.unblock} - /> - ); -} - -PreRenderer.propTypes = htmlRendererPropTypes; -PreRenderer.displayName = 'PreRenderer'; - -export default PreRenderer; diff --git a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.native.js b/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.native.js deleted file mode 100644 index b84dd43dd82f..000000000000 --- a/src/components/HTMLEngineProvider/HTMLRenderers/PreRenderer/index.native.js +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import htmlRendererPropTypes from '@components/HTMLEngineProvider/HTMLRenderers/htmlRendererPropTypes'; -import withLocalize from '@components/withLocalize'; -import BasePreRenderer from './BasePreRenderer'; - -function PreRenderer(props) { - // eslint-disable-next-line react/jsx-props-no-spreading - return ; -} - -PreRenderer.propTypes = htmlRendererPropTypes; -PreRenderer.displayName = 'PreRenderer'; - -export default withLocalize(PreRenderer); diff --git a/src/components/Header.tsx b/src/components/Header.tsx index 4eac2c7a6994..25532107016f 100644 --- a/src/components/Header.tsx +++ b/src/components/Header.tsx @@ -1,5 +1,7 @@ -import React, {ReactNode} from 'react'; -import {StyleProp, TextStyle, View} from 'react-native'; +import type {ReactNode} from 'react'; +import React from 'react'; +import type {StyleProp, TextStyle} from 'react-native'; +import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import EnvironmentBadge from './EnvironmentBadge'; import Text from './Text'; diff --git a/src/components/HeaderGap/types.ts b/src/components/HeaderGap/types.ts index 55a202c2e48c..1333ceac6198 100644 --- a/src/components/HeaderGap/types.ts +++ b/src/components/HeaderGap/types.ts @@ -1,5 +1,5 @@ -import {ReactNode} from 'react'; -import {StyleProp, ViewStyle} from 'react-native'; +import type {ReactNode} from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; type HeaderGapProps = { styles?: StyleProp; diff --git a/src/components/HeaderWithBackButton/index.tsx b/src/components/HeaderWithBackButton/index.tsx index 9ec8bca55a95..209803f2a5d1 100755 --- a/src/components/HeaderWithBackButton/index.tsx +++ b/src/components/HeaderWithBackButton/index.tsx @@ -19,7 +19,7 @@ import getButtonState from '@libs/getButtonState'; import Navigation from '@libs/Navigation/Navigation'; import CONST from '@src/CONST'; import ROUTES from '@src/ROUTES'; -import HeaderWithBackButtonProps from './types'; +import type HeaderWithBackButtonProps from './types'; function HeaderWithBackButton({ iconFill, diff --git a/src/components/HeaderWithBackButton/types.ts b/src/components/HeaderWithBackButton/types.ts index 99e93e8d18d2..9ffb0b5ef2f3 100644 --- a/src/components/HeaderWithBackButton/types.ts +++ b/src/components/HeaderWithBackButton/types.ts @@ -1,5 +1,5 @@ -import {ReactNode} from 'react'; -import {OnyxCollection, OnyxEntry} from 'react-native-onyx'; +import type {ReactNode} from 'react'; +import type {OnyxCollection, OnyxEntry} from 'react-native-onyx'; import type {Action} from '@hooks/useSingleExecution'; import type {StepCounterParams} from '@src/languages/types'; import type {AnchorPosition} from '@src/styles'; diff --git a/src/components/Hoverable/index.native.tsx b/src/components/Hoverable/index.native.tsx index b3d49db9d96e..47268e0f440a 100644 --- a/src/components/Hoverable/index.native.tsx +++ b/src/components/Hoverable/index.native.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {View} from 'react-native'; -import HoverableProps from './types'; +import type HoverableProps from './types'; /** * On mobile, there is no concept of hovering, so we return a plain wrapper around the component's children, diff --git a/src/components/Hoverable/index.tsx b/src/components/Hoverable/index.tsx index 9c641cfc19be..c82ba659593a 100644 --- a/src/components/Hoverable/index.tsx +++ b/src/components/Hoverable/index.tsx @@ -1,8 +1,9 @@ -import React, {ForwardedRef, forwardRef, MutableRefObject, ReactElement, RefAttributes, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; +import type {ForwardedRef, MutableRefObject, ReactElement, RefAttributes} from 'react'; +import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {DeviceEventEmitter} from 'react-native'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import CONST from '@src/CONST'; -import HoverableProps from './types'; +import type HoverableProps from './types'; /** * Maps the children of a Hoverable component to @@ -145,7 +146,7 @@ function Hoverable( // Expose inner ref to parent through outerRef. This enable us to use ref both in parent and child. useImperativeHandle(outerRef, () => ref.current, []); - const child = useMemo(() => React.Children.only(mapChildren(children, isHovered)), [children, isHovered]); + const child = useMemo(() => React.Children.only(mapChildren(children as ReactElement, isHovered)), [children, isHovered]); const enableHoveredOnMouseEnter = useCallback( (event: MouseEvent) => { diff --git a/src/components/Hoverable/types.ts b/src/components/Hoverable/types.ts index 430b865f50c5..921772743ab4 100644 --- a/src/components/Hoverable/types.ts +++ b/src/components/Hoverable/types.ts @@ -1,8 +1,8 @@ -import {ReactElement} from 'react'; +import type {ReactNode} from 'react'; type HoverableProps = { /** Children to wrap with Hoverable. */ - children: ((isHovered: boolean) => ReactElement) | ReactElement; + children: ((isHovered: boolean) => ReactNode) | ReactNode; /** Whether to disable the hover action */ disabled?: boolean; diff --git a/src/components/IFrame.tsx b/src/components/IFrame.tsx index 7520ad869507..ab27597aeebd 100644 --- a/src/components/IFrame.tsx +++ b/src/components/IFrame.tsx @@ -1,7 +1,8 @@ import React, {useEffect, useState} from 'react'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import ONYXKEYS from '@src/ONYXKEYS'; -import {Session} from '@src/types/onyx'; +import type {Session} from '@src/types/onyx'; type OldDotIFrameOnyxProps = { session: OnyxEntry; diff --git a/src/components/Icon/BankIcons/index.native.ts b/src/components/Icon/BankIcons/index.native.ts index 2011b71588af..61721512ec04 100644 --- a/src/components/Icon/BankIcons/index.native.ts +++ b/src/components/Icon/BankIcons/index.native.ts @@ -1,9 +1,10 @@ import GenericBank from '@assets/images/bankicons/generic-bank-account.svg'; import GenericBankCard from '@assets/images/cardicons/generic-bank-card.svg'; -import {BankIconParams, getBankIconAsset, getBankNameKey} from '@components/Icon/BankIconsUtils'; +import type {BankIconParams} from '@components/Icon/BankIconsUtils'; +import {getBankIconAsset, getBankNameKey} from '@components/Icon/BankIconsUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; -import {BankIcon} from '@src/types/onyx/Bank'; +import type {BankIcon} from '@src/types/onyx/Bank'; /** * Returns Bank Icon Object that matches to existing bank icons or default icons diff --git a/src/components/Icon/BankIcons/index.ts b/src/components/Icon/BankIcons/index.ts index 0e0935b103e4..378a7107aa03 100644 --- a/src/components/Icon/BankIcons/index.ts +++ b/src/components/Icon/BankIcons/index.ts @@ -1,10 +1,11 @@ import GenericBank from '@assets/images/bankicons/generic-bank-account.svg'; import GenericBankCard from '@assets/images/cardicons/generic-bank-card.svg'; -import {BankIconParams, getBankIconAsset, getBankNameKey} from '@components/Icon/BankIconsUtils'; +import type {BankIconParams} from '@components/Icon/BankIconsUtils'; +import {getBankIconAsset, getBankNameKey} from '@components/Icon/BankIconsUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; -import {BankIcon} from '@src/types/onyx/Bank'; -import IconAsset from '@src/types/utils/IconAsset'; +import type {BankIcon} from '@src/types/onyx/Bank'; +import type IconAsset from '@src/types/utils/IconAsset'; /** * It's a wrapper type for a bank icon asset. Bank icons are imported using require(), on the web platform after importing in this way it's necessary to use the property "default" diff --git a/src/components/Icon/BankIconsUtils.ts b/src/components/Icon/BankIconsUtils.ts index cc877ef9ef6f..c91ec030c9ba 100644 --- a/src/components/Icon/BankIconsUtils.ts +++ b/src/components/Icon/BankIconsUtils.ts @@ -1,7 +1,7 @@ -import {type ThemeStyles} from '@styles/index'; +import type {ThemeStyles} from '@styles/index'; import CONST from '@src/CONST'; -import {BankName, BankNameKey} from '@src/types/onyx/Bank'; -import IconAsset from '@src/types/utils/IconAsset'; +import type {BankName, BankNameKey} from '@src/types/onyx/Bank'; +import type IconAsset from '@src/types/utils/IconAsset'; type BankIconParams = { styles: ThemeStyles; diff --git a/src/components/Icon/IconWrapperStyles/index.ios.ts b/src/components/Icon/IconWrapperStyles/index.ios.ts index 9507502d9d26..b039cff95f58 100644 --- a/src/components/Icon/IconWrapperStyles/index.ios.ts +++ b/src/components/Icon/IconWrapperStyles/index.ios.ts @@ -1,4 +1,4 @@ -import IconWrapperStyle from './types'; +import type IconWrapperStyle from './types'; const style: IconWrapperStyle = { top: 1, diff --git a/src/components/Icon/IconWrapperStyles/index.ts b/src/components/Icon/IconWrapperStyles/index.ts index 541a2c296c17..2c4be7b706e8 100644 --- a/src/components/Icon/IconWrapperStyles/index.ts +++ b/src/components/Icon/IconWrapperStyles/index.ts @@ -1,4 +1,4 @@ -import IconWrapperStyle from './types'; +import type IconWrapperStyle from './types'; const style: IconWrapperStyle = { top: 2, diff --git a/src/components/Icon/index.tsx b/src/components/Icon/index.tsx index 732fe90deae2..20f3fd4a8acb 100644 --- a/src/components/Icon/index.tsx +++ b/src/components/Icon/index.tsx @@ -1,11 +1,12 @@ -import {ImageContentFit} from 'expo-image'; +import type {ImageContentFit} from 'expo-image'; import React from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import ImageSVG from '@components/ImageSVG'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; -import IconAsset from '@src/types/utils/IconAsset'; +import type IconAsset from '@src/types/utils/IconAsset'; import IconWrapperStyles from './IconWrapperStyles'; type IconProps = { diff --git a/src/components/ImageSVG/index.native.tsx b/src/components/ImageSVG/index.native.tsx index c397e047b7e0..bdd0b3830a22 100644 --- a/src/components/ImageSVG/index.native.tsx +++ b/src/components/ImageSVG/index.native.tsx @@ -1,7 +1,7 @@ import {Image} from 'expo-image'; import React from 'react'; -import {ImageSourcePropType} from 'react-native'; -import ImageSVGProps from './types'; +import type {ImageSourcePropType} from 'react-native'; +import type ImageSVGProps from './types'; function ImageSVG({src, width = '100%', height = '100%', fill, contentFit = 'cover', style}: ImageSVGProps) { const tintColorProp = fill ? {tintColor: fill} : {}; diff --git a/src/components/ImageSVG/index.tsx b/src/components/ImageSVG/index.tsx index 32d83fdcadd3..3ce04a1a190a 100644 --- a/src/components/ImageSVG/index.tsx +++ b/src/components/ImageSVG/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import {SvgProps} from 'react-native-svg'; -import ImageSVGProps from './types'; +import type {SvgProps} from 'react-native-svg'; +import type ImageSVGProps from './types'; function ImageSVG({src, width = '100%', height = '100%', fill, hovered = false, pressed = false, style, pointerEvents, preserveAspectRatio}: ImageSVGProps) { const ImageSvgComponent = src as React.FC; diff --git a/src/components/ImageSVG/types.ts b/src/components/ImageSVG/types.ts index cc580651c30c..341934852303 100644 --- a/src/components/ImageSVG/types.ts +++ b/src/components/ImageSVG/types.ts @@ -1,6 +1,6 @@ -import {ImageContentFit, ImageStyle} from 'expo-image'; -import {StyleProp, ViewStyle} from 'react-native'; -import IconAsset from '@src/types/utils/IconAsset'; +import type {ImageContentFit, ImageStyle} from 'expo-image'; +import type {StyleProp, ViewStyle} from 'react-native'; +import type IconAsset from '@src/types/utils/IconAsset'; type ImageSVGProps = { /** The asset to render. */ diff --git a/src/components/ImageWithSizeCalculation.tsx b/src/components/ImageWithSizeCalculation.tsx index af803bb360ee..b13d863d97e1 100644 --- a/src/components/ImageWithSizeCalculation.tsx +++ b/src/components/ImageWithSizeCalculation.tsx @@ -1,6 +1,7 @@ import delay from 'lodash/delay'; import React, {useEffect, useRef, useState} from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import Log from '@libs/Log'; import FullscreenLoadingIndicator from './FullscreenLoadingIndicator'; diff --git a/src/components/Indicator.tsx b/src/components/Indicator.tsx index 7b7eae97fd86..5bf93eb8a6b3 100644 --- a/src/components/Indicator.tsx +++ b/src/components/Indicator.tsx @@ -1,7 +1,8 @@ import React from 'react'; import {StyleSheet, View} from 'react-native'; -import {OnyxCollection, withOnyx} from 'react-native-onyx'; -import {OnyxEntry} from 'react-native-onyx/lib/types'; +import type {OnyxCollection} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx/lib/types'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import * as PolicyUtils from '@libs/PolicyUtils'; diff --git a/src/components/InlineCodeBlock/WrappedText.tsx b/src/components/InlineCodeBlock/WrappedText.tsx index 1c66cef234ed..3c196f2a7492 100644 --- a/src/components/InlineCodeBlock/WrappedText.tsx +++ b/src/components/InlineCodeBlock/WrappedText.tsx @@ -1,5 +1,6 @@ import React, {Fragment} from 'react'; -import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'; +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import Text from '@components/Text'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; diff --git a/src/components/InlineCodeBlock/types.ts b/src/components/InlineCodeBlock/types.ts index a100177e41a7..ae847b293a60 100644 --- a/src/components/InlineCodeBlock/types.ts +++ b/src/components/InlineCodeBlock/types.ts @@ -1,4 +1,4 @@ -import {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import type {TDefaultRenderer, TDefaultRendererProps, TText} from 'react-native-render-html'; type InlineCodeBlockProps = { diff --git a/src/components/InvertedFlatList/BaseInvertedFlatList.tsx b/src/components/InvertedFlatList/BaseInvertedFlatList.tsx index 08d990583572..9e3991828625 100644 --- a/src/components/InvertedFlatList/BaseInvertedFlatList.tsx +++ b/src/components/InvertedFlatList/BaseInvertedFlatList.tsx @@ -1,5 +1,6 @@ -import React, {ForwardedRef, forwardRef} from 'react'; -import {FlatListProps} from 'react-native'; +import type {ForwardedRef} from 'react'; +import React, {forwardRef} from 'react'; +import type {FlatListProps} from 'react-native'; import FlatList from '@components/FlatList'; const AUTOSCROLL_TO_TOP_THRESHOLD = 128; diff --git a/src/components/InvertedFlatList/CellRendererComponent.tsx b/src/components/InvertedFlatList/CellRendererComponent.tsx index 252d47989064..b95fbf42cbb4 100644 --- a/src/components/InvertedFlatList/CellRendererComponent.tsx +++ b/src/components/InvertedFlatList/CellRendererComponent.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import {StyleProp, View, ViewProps} from 'react-native'; +import type {StyleProp, ViewProps} from 'react-native'; +import {View} from 'react-native'; type CellRendererComponentProps = ViewProps & { index: number; diff --git a/src/components/InvertedFlatList/index.native.tsx b/src/components/InvertedFlatList/index.native.tsx index 906ef6d6ae94..70cabf5a536a 100644 --- a/src/components/InvertedFlatList/index.native.tsx +++ b/src/components/InvertedFlatList/index.native.tsx @@ -1,17 +1,15 @@ -import React, {ForwardedRef, forwardRef} from 'react'; -import {FlatList, FlatListProps} from 'react-native'; -import useThemeStyles from '@hooks/useThemeStyles'; +import type {ForwardedRef} from 'react'; +import React, {forwardRef} from 'react'; +import type {FlatList, FlatListProps} from 'react-native'; import BaseInvertedFlatList from './BaseInvertedFlatList'; import CellRendererComponent from './CellRendererComponent'; function BaseInvertedFlatListWithRef(props: FlatListProps, ref: ForwardedRef) { - const styles = useThemeStyles(); return ( ({onScroll: onScrollProp = () => {}, contentContainerStyle, ...props}: FlatListProps, ref: ForwardedRef) { +function InvertedFlatList({onScroll: onScrollProp = () => {}, ...props}: FlatListProps, ref: ForwardedRef) { const lastScrollEvent = useRef(null); const scrollEndTimeout = useRef(null); const updateInProgress = useRef(false); @@ -84,7 +86,6 @@ function InvertedFlatList({onScroll: onScrollProp = () => {}, contentContaine // eslint-disable-next-line react/jsx-props-no-spreading {...props} ref={ref} - contentContainerStyle={contentContainerStyle} onScroll={handleScroll} /> ); diff --git a/src/components/KeyboardAvoidingView/index.ios.tsx b/src/components/KeyboardAvoidingView/index.ios.tsx index fde4df6fc05b..a7cd767377ef 100644 --- a/src/components/KeyboardAvoidingView/index.ios.tsx +++ b/src/components/KeyboardAvoidingView/index.ios.tsx @@ -3,7 +3,7 @@ */ import React from 'react'; import {KeyboardAvoidingView as KeyboardAvoidingViewComponent} from 'react-native'; -import KeyboardAvoidingViewProps from './types'; +import type KeyboardAvoidingViewProps from './types'; function KeyboardAvoidingView(props: KeyboardAvoidingViewProps) { // eslint-disable-next-line react/jsx-props-no-spreading diff --git a/src/components/KeyboardAvoidingView/index.tsx b/src/components/KeyboardAvoidingView/index.tsx index cf3a5c5ebef7..09ec21e5b219 100644 --- a/src/components/KeyboardAvoidingView/index.tsx +++ b/src/components/KeyboardAvoidingView/index.tsx @@ -3,7 +3,7 @@ */ import React from 'react'; import {View} from 'react-native'; -import KeyboardAvoidingViewProps from './types'; +import type KeyboardAvoidingViewProps from './types'; function KeyboardAvoidingView(props: KeyboardAvoidingViewProps) { const {behavior, contentContainerStyle, enabled, keyboardVerticalOffset, ...rest} = props; diff --git a/src/components/Lightbox.js b/src/components/Lightbox.js index 06f8ee4cfeb6..45326edb4610 100644 --- a/src/components/Lightbox.js +++ b/src/components/Lightbox.js @@ -183,8 +183,8 @@ function Lightbox({isAuthTokenRequired, source, onScaleChanged, onPress, onError onError={onError} onLoadEnd={() => setImageLoaded(true)} onLoad={(e) => { - const width = (e.nativeEvent?.width || 0) / PixelRatio.get(); - const height = (e.nativeEvent?.height || 0) / PixelRatio.get(); + const width = (e.nativeEvent?.width || 0) * PixelRatio.get(); + const height = (e.nativeEvent?.height || 0) * PixelRatio.get(); setImageDimensions({...imageDimensions, lightboxSize: {width, height}}); }} /> @@ -205,8 +205,8 @@ function Lightbox({isAuthTokenRequired, source, onScaleChanged, onPress, onError isAuthTokenRequired={isAuthTokenRequired} onLoadEnd={() => setFallbackLoaded(true)} onLoad={(e) => { - const width = e.nativeEvent?.width || 0; - const height = e.nativeEvent?.height || 0; + const width = (e.nativeEvent?.width || 0) * PixelRatio.get(); + const height = (e.nativeEvent?.height || 0) * PixelRatio.get(); if (imageDimensions?.lightboxSize != null) { return; diff --git a/src/components/LinearGradient/index.native.ts b/src/components/LinearGradient/index.native.ts index 46bed24ebc10..29af26b96b64 100644 --- a/src/components/LinearGradient/index.native.ts +++ b/src/components/LinearGradient/index.native.ts @@ -1,5 +1,5 @@ import LinearGradientNative from 'react-native-linear-gradient'; -import LinearGradient from './types'; +import type LinearGradient from './types'; const LinearGradientImplementation: LinearGradient = LinearGradientNative; diff --git a/src/components/LinearGradient/index.ts b/src/components/LinearGradient/index.ts index 7246ccf2fb69..84d0fc2ce5c8 100644 --- a/src/components/LinearGradient/index.ts +++ b/src/components/LinearGradient/index.ts @@ -1,5 +1,5 @@ import LinearGradientWeb from 'react-native-web-linear-gradient'; -import LinearGradient from './types'; +import type LinearGradient from './types'; const LinearGradientImplementation: LinearGradient = LinearGradientWeb; diff --git a/src/components/LinearGradient/types.ts b/src/components/LinearGradient/types.ts index cf6661eaecaa..9e238ef71f12 100644 --- a/src/components/LinearGradient/types.ts +++ b/src/components/LinearGradient/types.ts @@ -1,4 +1,4 @@ -import LinearGradientNative from 'react-native-linear-gradient'; +import type LinearGradientNative from 'react-native-linear-gradient'; type LinearGradient = typeof LinearGradientNative; diff --git a/src/components/LocaleContextProvider.tsx b/src/components/LocaleContextProvider.tsx index 3c649a8cb546..7313bb4aa7bb 100644 --- a/src/components/LocaleContextProvider.tsx +++ b/src/components/LocaleContextProvider.tsx @@ -1,6 +1,7 @@ import React, {createContext, useMemo} from 'react'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; -import {ValueOf} from 'type-fest'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; +import type {ValueOf} from 'type-fest'; import compose from '@libs/compose'; import DateUtils from '@libs/DateUtils'; import * as LocaleDigitUtils from '@libs/LocaleDigitUtils'; @@ -8,9 +9,10 @@ import * as LocalePhoneNumber from '@libs/LocalePhoneNumber'; import * as Localize from '@libs/Localize'; import * as NumberFormatUtils from '@libs/NumberFormatUtils'; import CONST from '@src/CONST'; -import {TranslationPaths} from '@src/languages/types'; +import type {TranslationPaths} from '@src/languages/types'; import ONYXKEYS from '@src/ONYXKEYS'; -import withCurrentUserPersonalDetails, {WithCurrentUserPersonalDetailsProps} from './withCurrentUserPersonalDetails'; +import type {WithCurrentUserPersonalDetailsProps} from './withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails from './withCurrentUserPersonalDetails'; type Locale = ValueOf; diff --git a/src/components/LocalePicker.tsx b/src/components/LocalePicker.tsx index 46adb1a4895e..3a2d9a0fd7b9 100644 --- a/src/components/LocalePicker.tsx +++ b/src/components/LocalePicker.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import type {OnyxEntry} from 'react-native-onyx'; +import {withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/components/LocationErrorMessage/BaseLocationErrorMessage.js b/src/components/LocationErrorMessage/BaseLocationErrorMessage.tsx similarity index 81% rename from src/components/LocationErrorMessage/BaseLocationErrorMessage.js rename to src/components/LocationErrorMessage/BaseLocationErrorMessage.tsx index d90783d94ad5..33a0bba7481c 100644 --- a/src/components/LocationErrorMessage/BaseLocationErrorMessage.js +++ b/src/components/LocationErrorMessage/BaseLocationErrorMessage.tsx @@ -1,4 +1,3 @@ -import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; import Icon from '@components/Icon'; @@ -7,29 +6,25 @@ import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeed import Text from '@components/Text'; import TextLink from '@components/TextLink'; import Tooltip from '@components/Tooltip'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import colors from '@styles/theme/colors'; import CONST from '@src/CONST'; -import * as locationErrorMessagePropTypes from './locationErrorMessagePropTypes'; +import type LocationErrorMessageProps from './types'; -const propTypes = { +type BaseLocationErrorMessageProps = LocationErrorMessageProps & { /** A callback that runs when 'allow location permission' link is pressed */ - onAllowLocationLinkPress: PropTypes.func.isRequired, - - // eslint-disable-next-line react/forbid-foreign-prop-types - ...locationErrorMessagePropTypes.propTypes, - - /* Onyx Props */ - ...withLocalizePropTypes, + onAllowLocationLinkPress: () => void; }; -function BaseLocationErrorMessage({onClose, onAllowLocationLinkPress, locationErrorCode, translate}) { +function BaseLocationErrorMessage({onClose, onAllowLocationLinkPress, locationErrorCode}: BaseLocationErrorMessageProps) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); + const {translate} = useLocalize(); + if (!locationErrorCode) { return null; } @@ -81,6 +76,5 @@ function BaseLocationErrorMessage({onClose, onAllowLocationLinkPress, locationEr } BaseLocationErrorMessage.displayName = 'BaseLocationErrorMessage'; -BaseLocationErrorMessage.propTypes = propTypes; -BaseLocationErrorMessage.defaultProps = locationErrorMessagePropTypes.defaultProps; -export default withLocalize(BaseLocationErrorMessage); + +export default BaseLocationErrorMessage; diff --git a/src/components/LocationErrorMessage/index.native.js b/src/components/LocationErrorMessage/index.native.tsx similarity index 67% rename from src/components/LocationErrorMessage/index.native.js rename to src/components/LocationErrorMessage/index.native.tsx index 467018538b6e..7936fff73c06 100644 --- a/src/components/LocationErrorMessage/index.native.js +++ b/src/components/LocationErrorMessage/index.native.tsx @@ -1,14 +1,14 @@ import React from 'react'; import {Linking} from 'react-native'; import BaseLocationErrorMessage from './BaseLocationErrorMessage'; -import * as locationErrorMessagePropTypes from './locationErrorMessagePropTypes'; +import type LocationErrorMessageProps from './types'; /** Opens app level settings from the native system settings */ const openAppSettings = () => { Linking.openSettings(); }; -function LocationErrorMessage(props) { +function LocationErrorMessage(props: LocationErrorMessageProps) { return ( { Linking.openURL(CONST.NEWHELP_URL); }; -function LocationErrorMessage(props) { +function LocationErrorMessage(props: LocationErrorMessageProps) { return ( void; /** * The location error code from onyx @@ -11,11 +9,7 @@ const propTypes = { * - code 2 = location is unavailable or there is some connection issue * - code 3 = location fetch timeout */ - locationErrorCode: PropTypes.oneOf([-1, 1, 2, 3]), -}; - -const defaultProps = { - locationErrorCode: null, + locationErrorCode?: -1 | 1 | 2 | 3; }; -export {propTypes, defaultProps}; +export default LocationErrorMessageProps; diff --git a/src/components/Lottie/index.tsx b/src/components/Lottie/index.tsx index 2bf7df056c4d..5c672cf7cab6 100644 --- a/src/components/Lottie/index.tsx +++ b/src/components/Lottie/index.tsx @@ -1,7 +1,9 @@ -import LottieView, {LottieViewProps} from 'lottie-react-native'; -import React, {ForwardedRef, forwardRef} from 'react'; +import type {LottieViewProps} from 'lottie-react-native'; +import LottieView from 'lottie-react-native'; +import type {ForwardedRef} from 'react'; +import React, {forwardRef} from 'react'; import {View} from 'react-native'; -import DotLottieAnimation from '@components/LottieAnimations/types'; +import type DotLottieAnimation from '@components/LottieAnimations/types'; import useNetwork from '@hooks/useNetwork'; import useThemeStyles from '@hooks/useThemeStyles'; diff --git a/src/components/LottieAnimations/index.tsx b/src/components/LottieAnimations/index.tsx index 078cdc0c6987..0d2cac253135 100644 --- a/src/components/LottieAnimations/index.tsx +++ b/src/components/LottieAnimations/index.tsx @@ -1,4 +1,4 @@ -import DotLottieAnimation from './types'; +import type DotLottieAnimation from './types'; const DotLottieAnimations: Record = { ExpensifyLounge: { diff --git a/src/components/LottieAnimations/types.ts b/src/components/LottieAnimations/types.ts index fcd793c6dfed..6000b9f853f0 100644 --- a/src/components/LottieAnimations/types.ts +++ b/src/components/LottieAnimations/types.ts @@ -1,4 +1,4 @@ -import {LottieViewProps} from 'lottie-react-native'; +import type {LottieViewProps} from 'lottie-react-native'; type DotLottieAnimation = { file: LottieViewProps['source']; diff --git a/src/components/MapView/Direction.tsx b/src/components/MapView/Direction.tsx index b3162149a48d..729d3c71e91f 100644 --- a/src/components/MapView/Direction.tsx +++ b/src/components/MapView/Direction.tsx @@ -1,6 +1,6 @@ import Mapbox from '@rnmapbox/maps'; import useThemeStyles from '@hooks/useThemeStyles'; -import {DirectionProps} from './MapViewTypes'; +import type {DirectionProps} from './MapViewTypes'; function Direction({coordinates}: DirectionProps) { const styles = useThemeStyles(); diff --git a/src/components/MapView/Direction.website.tsx b/src/components/MapView/Direction.website.tsx index f85bda125473..02fee83042a2 100644 --- a/src/components/MapView/Direction.website.tsx +++ b/src/components/MapView/Direction.website.tsx @@ -6,7 +6,7 @@ import React from 'react'; import {Layer, Source} from 'react-map-gl'; import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; -import {DirectionProps} from './MapViewTypes'; +import type {DirectionProps} from './MapViewTypes'; function Direction({coordinates}: DirectionProps) { const styles = useThemeStyles(); diff --git a/src/components/MapView/MapView.tsx b/src/components/MapView/MapView.tsx index f7a15ba10d2c..6321b461f21e 100644 --- a/src/components/MapView/MapView.tsx +++ b/src/components/MapView/MapView.tsx @@ -1,5 +1,6 @@ import {useFocusEffect, useNavigation} from '@react-navigation/native'; -import Mapbox, {MapState, MarkerView, setAccessToken} from '@rnmapbox/maps'; +import type {MapState} from '@rnmapbox/maps'; +import Mapbox, {MarkerView, setAccessToken} from '@rnmapbox/maps'; import {forwardRef, memo, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; @@ -12,10 +13,10 @@ import useLocalize from '@src/hooks/useLocalize'; import useNetwork from '@src/hooks/useNetwork'; import ONYXKEYS from '@src/ONYXKEYS'; import Direction from './Direction'; -import {MapViewHandle} from './MapViewTypes'; +import type {MapViewHandle} from './MapViewTypes'; import PendingMapView from './PendingMapView'; import responder from './responder'; -import {ComponentProps, MapViewOnyxProps} from './types'; +import type {ComponentProps, MapViewOnyxProps} from './types'; import utils from './utils'; const MapView = forwardRef( diff --git a/src/components/MapView/MapView.website.tsx b/src/components/MapView/MapView.website.tsx index 82909001fefd..05d86e8ec999 100644 --- a/src/components/MapView/MapView.website.tsx +++ b/src/components/MapView/MapView.website.tsx @@ -6,7 +6,8 @@ import {useFocusEffect} from '@react-navigation/native'; import mapboxgl from 'mapbox-gl'; import 'mapbox-gl/dist/mapbox-gl.css'; import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState} from 'react'; -import Map, {MapRef, Marker} from 'react-map-gl'; +import type {MapRef} from 'react-map-gl'; +import Map, {Marker} from 'react-map-gl'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import useStyleUtils from '@hooks/useStyleUtils'; @@ -20,10 +21,10 @@ import getCurrentPosition from '@src/libs/getCurrentPosition'; import ONYXKEYS from '@src/ONYXKEYS'; import Direction from './Direction'; import './mapbox.css'; -import {MapViewHandle} from './MapViewTypes'; +import type {MapViewHandle} from './MapViewTypes'; import PendingMapView from './PendingMapView'; import responder from './responder'; -import {ComponentProps, MapViewOnyxProps} from './types'; +import type {ComponentProps, MapViewOnyxProps} from './types'; import utils from './utils'; const MapView = forwardRef( diff --git a/src/components/MapView/MapViewTypes.ts b/src/components/MapView/MapViewTypes.ts index 6cc52ac91d18..4b9ab6af233b 100644 --- a/src/components/MapView/MapViewTypes.ts +++ b/src/components/MapView/MapViewTypes.ts @@ -1,4 +1,4 @@ -import {ComponentType} from 'react'; +import type {ComponentType} from 'react'; import type {StyleProp, ViewStyle} from 'react-native'; type MapViewProps = { diff --git a/src/components/MapView/PendingMapView.tsx b/src/components/MapView/PendingMapView.tsx index 0af816785e9a..32bf42a14b10 100644 --- a/src/components/MapView/PendingMapView.tsx +++ b/src/components/MapView/PendingMapView.tsx @@ -6,7 +6,7 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; -import {PendingMapViewProps} from './MapViewTypes'; +import type {PendingMapViewProps} from './MapViewTypes'; function PendingMapView({title = '', subtitle = '', style}: PendingMapViewProps) { const hasTextContent = !_.isEmpty(title) || !_.isEmpty(subtitle); diff --git a/src/components/MapView/types.ts b/src/components/MapView/types.ts index 2c8b9240c445..a0494a9ac499 100644 --- a/src/components/MapView/types.ts +++ b/src/components/MapView/types.ts @@ -1,6 +1,6 @@ -import {OnyxEntry} from 'react-native-onyx'; -import * as OnyxTypes from '@src/types/onyx'; -import {MapViewProps} from './MapViewTypes'; +import type {OnyxEntry} from 'react-native-onyx'; +import type * as OnyxTypes from '@src/types/onyx'; +import type {MapViewProps} from './MapViewTypes'; type MapViewOnyxProps = { userLocation: OnyxEntry; diff --git a/src/components/MentionSuggestions.tsx b/src/components/MentionSuggestions.tsx index 3e235a2fc88a..459131ecc434 100644 --- a/src/components/MentionSuggestions.tsx +++ b/src/components/MentionSuggestions.tsx @@ -5,7 +5,7 @@ import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import getStyledTextArray from '@libs/GetStyledTextArray'; import CONST from '@src/CONST'; -import {Icon} from '@src/types/onyx/OnyxCommon'; +import type {Icon} from '@src/types/onyx/OnyxCommon'; import AutoCompleteSuggestions from './AutoCompleteSuggestions'; import Avatar from './Avatar'; import Text from './Text'; diff --git a/src/components/MenuItem.tsx b/src/components/MenuItem.tsx index db150d55f0d2..34d60418d3ab 100644 --- a/src/components/MenuItem.tsx +++ b/src/components/MenuItem.tsx @@ -1,9 +1,11 @@ import ExpensiMark from 'expensify-common/lib/ExpensiMark'; -import {ImageContentFit} from 'expo-image'; -import React, {ForwardedRef, forwardRef, ReactNode, useEffect, useMemo, useRef, useState} from 'react'; -import {GestureResponderEvent, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; -import {AnimatedStyle} from 'react-native-reanimated'; -import {ValueOf} from 'type-fest'; +import type {ImageContentFit} from 'expo-image'; +import type {ForwardedRef, ReactNode} from 'react'; +import React, {forwardRef, useEffect, useMemo, useRef, useState} from 'react'; +import type {GestureResponderEvent, StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import type {AnimatedStyle} from 'react-native-reanimated'; +import type {ValueOf} from 'type-fest'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -12,16 +14,16 @@ import ControlSelection from '@libs/ControlSelection'; import convertToLTR from '@libs/convertToLTR'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import getButtonState from '@libs/getButtonState'; -import {AvatarSource} from '@libs/UserUtils'; +import type {AvatarSource} from '@libs/UserUtils'; import variables from '@styles/variables'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; -import {Icon as IconType} from '@src/types/onyx/OnyxCommon'; -import IconAsset from '@src/types/utils/IconAsset'; +import type {Icon as IconType} from '@src/types/onyx/OnyxCommon'; +import type IconAsset from '@src/types/utils/IconAsset'; import Avatar from './Avatar'; import Badge from './Badge'; import DisplayNames from './DisplayNames'; -import {DisplayNameWithTooltip} from './DisplayNames/types'; +import type {DisplayNameWithTooltip} from './DisplayNames/types'; import FormHelpMessage from './FormHelpMessage'; import Hoverable from './Hoverable'; import Icon from './Icon'; @@ -49,14 +51,14 @@ type UnresponsiveProps = { type IconProps = { /** Flag to choose between avatar image or an icon */ - iconType: typeof CONST.ICON_TYPE_ICON; + iconType?: typeof CONST.ICON_TYPE_ICON; /** Icon to display on the left side of component */ icon: IconAsset; }; type AvatarProps = { - iconType: typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE; + iconType?: typeof CONST.ICON_TYPE_AVATAR | typeof CONST.ICON_TYPE_WORKSPACE; icon: AvatarSource; }; @@ -85,7 +87,7 @@ type MenuItemProps = (ResponsiveProps | UnresponsiveProps) & titleStyle?: ViewStyle; /** Any adjustments to style when menu item is hovered or pressed */ - hoverAndPressStyle: StyleProp>; + hoverAndPressStyle?: StyleProp>; /** Additional styles to style the description text below the title */ descriptionTextStyle?: StyleProp; @@ -175,7 +177,7 @@ type MenuItemProps = (ResponsiveProps | UnresponsiveProps) & isSelected?: boolean; /** Prop to identify if we should load avatars vertically instead of diagonally */ - shouldStackHorizontally: boolean; + shouldStackHorizontally?: boolean; /** Prop to represent the size of the avatar images to be shown */ avatarSize?: (typeof CONST.AVATAR_SIZE)[keyof typeof CONST.AVATAR_SIZE]; @@ -220,10 +222,10 @@ type MenuItemProps = (ResponsiveProps | UnresponsiveProps) & furtherDetails?: string; /** The function that should be called when this component is LongPressed or right-clicked. */ - onSecondaryInteraction: () => void; + onSecondaryInteraction?: () => void; /** Array of objects that map display names to their corresponding tooltip */ - titleWithTooltips: DisplayNameWithTooltip[]; + titleWithTooltips?: DisplayNameWithTooltip[]; /** Icon should be displayed in its own color */ displayInDefaultIconColor?: boolean; @@ -523,17 +525,19 @@ function MenuItem( {error} )} - {furtherDetailsIcon && !!furtherDetails && ( + {!!furtherDetails && ( - + {!!furtherDetailsIcon && ( + + )} {furtherDetails} diff --git a/src/components/MenuItemWithTopDescription.tsx b/src/components/MenuItemWithTopDescription.tsx index 48fa95ecf637..0a48740de62e 100644 --- a/src/components/MenuItemWithTopDescription.tsx +++ b/src/components/MenuItemWithTopDescription.tsx @@ -1,5 +1,6 @@ -import React, {ForwardedRef, forwardRef} from 'react'; -import {View} from 'react-native'; +import type {ForwardedRef} from 'react'; +import React, {forwardRef} from 'react'; +import type {View} from 'react-native'; import MenuItem from './MenuItem'; import type {MenuItemProps} from './MenuItem'; diff --git a/src/components/MessagesRow.tsx b/src/components/MessagesRow.tsx index f49a0927de06..cfec6fd292e9 100644 --- a/src/components/MessagesRow.tsx +++ b/src/components/MessagesRow.tsx @@ -1,9 +1,10 @@ import React from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import * as Localize from '@libs/Localize'; +import type * as Localize from '@libs/Localize'; import CONST from '@src/CONST'; import {isEmptyObject} from '@src/types/utils/EmptyObject'; import DotIndicatorMessage from './DotIndicatorMessage'; diff --git a/src/components/Modal/BaseModal.tsx b/src/components/Modal/BaseModal.tsx index e03c32f6bfcc..6e5b4eddae9e 100644 --- a/src/components/Modal/BaseModal.tsx +++ b/src/components/Modal/BaseModal.tsx @@ -13,7 +13,7 @@ import useNativeDriver from '@libs/useNativeDriver'; import variables from '@styles/variables'; import * as Modal from '@userActions/Modal'; import CONST from '@src/CONST'; -import BaseModalProps from './types'; +import type BaseModalProps from './types'; function BaseModal( { diff --git a/src/components/Modal/index.android.tsx b/src/components/Modal/index.android.tsx index 2343cb4c70a9..4d7ae128a114 100644 --- a/src/components/Modal/index.android.tsx +++ b/src/components/Modal/index.android.tsx @@ -3,7 +3,7 @@ import {AppState} from 'react-native'; import withWindowDimensions from '@components/withWindowDimensions'; import ComposerFocusManager from '@libs/ComposerFocusManager'; import BaseModal from './BaseModal'; -import BaseModalProps from './types'; +import type BaseModalProps from './types'; AppState.addEventListener('focus', () => { ComposerFocusManager.setReadyToFocus(); diff --git a/src/components/Modal/index.ios.tsx b/src/components/Modal/index.ios.tsx index f780775ec216..cbe58a071d7d 100644 --- a/src/components/Modal/index.ios.tsx +++ b/src/components/Modal/index.ios.tsx @@ -1,7 +1,7 @@ import React from 'react'; import withWindowDimensions from '@components/withWindowDimensions'; import BaseModal from './BaseModal'; -import BaseModalProps from './types'; +import type BaseModalProps from './types'; function Modal({children, ...rest}: BaseModalProps) { return ( diff --git a/src/components/Modal/index.tsx b/src/components/Modal/index.tsx index 4269420dcd7f..56f3c76a8879 100644 --- a/src/components/Modal/index.tsx +++ b/src/components/Modal/index.tsx @@ -5,7 +5,7 @@ import useTheme from '@hooks/useTheme'; import StatusBar from '@libs/StatusBar'; import CONST from '@src/CONST'; import BaseModal from './BaseModal'; -import BaseModalProps from './types'; +import type BaseModalProps from './types'; function Modal({fullscreen = true, onModalHide = () => {}, type, onModalShow = () => {}, children, ...rest}: BaseModalProps) { const theme = useTheme(); diff --git a/src/components/Modal/types.ts b/src/components/Modal/types.ts index 461a5935eda9..0fed37ffea8b 100644 --- a/src/components/Modal/types.ts +++ b/src/components/Modal/types.ts @@ -1,8 +1,8 @@ -import {ViewStyle} from 'react-native'; -import {ModalProps} from 'react-native-modal'; -import {ValueOf} from 'type-fest'; -import {WindowDimensionsProps} from '@components/withWindowDimensions/types'; -import CONST from '@src/CONST'; +import type {ViewStyle} from 'react-native'; +import type {ModalProps} from 'react-native-modal'; +import type {ValueOf} from 'type-fest'; +import type {WindowDimensionsProps} from '@components/withWindowDimensions/types'; +import type CONST from '@src/CONST'; type PopoverAnchorPosition = { top?: number; diff --git a/src/components/MoneyReportHeaderStatusBar.tsx b/src/components/MoneyReportHeaderStatusBar.tsx index 9d96d93423c5..4b30276a204f 100644 --- a/src/components/MoneyReportHeaderStatusBar.tsx +++ b/src/components/MoneyReportHeaderStatusBar.tsx @@ -4,7 +4,7 @@ import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import * as NextStepUtils from '@libs/NextStepUtils'; import CONST from '@src/CONST'; -import ReportNextStep from '@src/types/onyx/ReportNextStep'; +import type ReportNextStep from '@src/types/onyx/ReportNextStep'; import RenderHTML from './RenderHTML'; type MoneyReportHeaderStatusBarProps = { diff --git a/src/components/MoneyRequestConfirmationList.js b/src/components/MoneyRequestConfirmationList.js index b75f4e2df845..13dce9337673 100755 --- a/src/components/MoneyRequestConfirmationList.js +++ b/src/components/MoneyRequestConfirmationList.js @@ -40,6 +40,7 @@ import SettlementButton from './SettlementButton'; import ShowMoreButton from './ShowMoreButton'; import Switch from './Switch'; import tagPropTypes from './tagPropTypes'; +import taxPropTypes from './taxPropTypes'; import Text from './Text'; import transactionPropTypes from './transactionPropTypes'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails'; @@ -164,6 +165,10 @@ const propTypes = { /** Collection of tags attached to a policy */ policyTags: tagPropTypes, + /* Onyx Props */ + /** Collection of tax rates attached to a policy */ + policyTaxRates: taxPropTypes, + /** Holds data related to Money Request view state, rather than the underlying Money Request data. */ iou: iouPropTypes, }; @@ -200,6 +205,7 @@ const defaultProps = { shouldShowSmartScanFields: true, isPolicyExpenseChat: false, iou: iouDefaultProps, + policyTaxRates: {}, }; function MoneyRequestConfirmationList(props) { @@ -241,6 +247,9 @@ function MoneyRequestConfirmationList(props) { // A flag for showing the tags field const shouldShowTags = props.isPolicyExpenseChat && (props.iouTag || OptionsListUtils.hasEnabledOptions(_.values(policyTagList))); + // A flag for showing tax fields - tax rate and tax amount + const shouldShowTax = props.isPolicyExpenseChat && props.policy.isTaxTrackingEnabled; + // A flag for showing the billable field const shouldShowBillable = !lodashGet(props.policy, 'disabledFields.defaultBillable', true); @@ -252,6 +261,11 @@ function MoneyRequestConfirmationList(props) { shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : props.iouAmount, props.isDistanceRequest ? currency : props.iouCurrencyCode, ); + const formattedTaxAmount = CurrencyUtils.convertToDisplayString(props.transaction.taxAmount, props.iouCurrencyCode); + + const defaultTaxKey = props.policyTaxRates.defaultExternalID; + const defaultTaxName = (defaultTaxKey && `${props.policyTaxRates.taxes[defaultTaxKey].name} (${props.policyTaxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || ''; + const taxRateTitle = (props.transaction.taxRate && props.transaction.taxRate.text) || defaultTaxName; const isFocused = useIsFocused(); const [formError, setFormError] = useState(''); @@ -741,6 +755,40 @@ function MoneyRequestConfirmationList(props) { /> )} + {shouldShowTax && ( + + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(props.iouType, props.transaction.transactionID, props.reportID, Navigation.getActiveRouteWithoutParams()), + ) + } + disabled={didConfirm} + interactive={!props.isReadOnly} + /> + )} + + {shouldShowTax && ( + + Navigation.navigate( + ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(props.iouType, props.transaction.transactionID, props.reportID, Navigation.getActiveRouteWithoutParams()), + ) + } + disabled={didConfirm} + interactive={!props.isReadOnly} + /> + )} + {shouldShowBillable && ( {translate('common.billable')} @@ -777,12 +825,15 @@ export default compose( key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, selector: DistanceRequestUtils.getDefaultMileageRate, }, - draftTransaction: { + splitTransactionDraft: { key: ({transactionID}) => `${ONYXKEYS.COLLECTION.SPLIT_TRANSACTION_DRAFT}${transactionID}`, }, policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, }, + policyTaxRates: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${policyID}`, + }, iou: { key: ONYXKEYS.IOU, }, diff --git a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js index ecd95620c498..7ec95aec951f 100755 --- a/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js +++ b/src/components/MoneyTemporaryForRefactorRequestConfirmationList.js @@ -39,6 +39,7 @@ import OptionsSelector from './OptionsSelector'; import SettlementButton from './SettlementButton'; import Switch from './Switch'; import tagPropTypes from './tagPropTypes'; +import taxPropTypes from './taxPropTypes'; import Text from './Text'; import transactionPropTypes from './transactionPropTypes'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from './withCurrentUserPersonalDetails'; @@ -160,6 +161,10 @@ const propTypes = { /** Collection of tags attached to a policy */ policyTags: tagPropTypes, + /* Onyx Props */ + /** Collection of tax rates attached to a policy */ + policyTaxRates: taxPropTypes, + /** Transaction that represents the money request */ transaction: transactionPropTypes, }; @@ -194,6 +199,7 @@ const defaultProps = { isDistanceRequest: false, shouldShowSmartScanFields: true, isPolicyExpenseChat: false, + policyTaxRates: {}, }; function MoneyTemporaryForRefactorRequestConfirmationList({ @@ -235,6 +241,7 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ session: {accountID}, shouldShowSmartScanFields, transaction, + policyTaxRates, }) { const theme = useTheme(); const styles = useThemeStyles(); @@ -269,6 +276,9 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ // A flag for showing the tags field const shouldShowTags = isPolicyExpenseChat && OptionsListUtils.hasEnabledOptions(_.values(policyTagList)); + // A flag for showing tax rate + const shouldShowTax = isPolicyExpenseChat && policy.isTaxTrackingEnabled; + // A flag for showing the billable field const shouldShowBillable = !lodashGet(policy, 'disabledFields.defaultBillable', true); @@ -280,6 +290,11 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ shouldCalculateDistanceAmount ? DistanceRequestUtils.getDistanceRequestAmount(distance, unit, rate) : iouAmount, isDistanceRequest ? currency : iouCurrencyCode, ); + const formattedTaxAmount = CurrencyUtils.convertToDisplayString(transaction.taxAmount, iouCurrencyCode); + + const defaultTaxKey = policyTaxRates.defaultExternalID; + const defaultTaxName = (defaultTaxKey && `${policyTaxRates.taxes[defaultTaxKey].name} (${policyTaxRates.taxes[defaultTaxKey].value}) • ${translate('common.default')}`) || ''; + const taxRateTitle = (transaction.taxRate && transaction.taxRate.text) || defaultTaxName; const isFocused = useIsFocused(); const [formError, setFormError] = useState(''); @@ -796,6 +811,35 @@ function MoneyTemporaryForRefactorRequestConfirmationList({ rightLabel={canUseViolations && Boolean(policy.requiresTag) ? translate('common.required') : ''} /> )} + {shouldShowTax && ( + + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_RATE.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())) + } + disabled={didConfirm} + interactive={!isReadOnly} + /> + )} + + {shouldShowTax && ( + + Navigation.navigate(ROUTES.MONEY_REQUEST_STEP_TAX_AMOUNT.getRoute(iouType, transaction.transactionID, reportID, Navigation.getActiveRouteWithoutParams())) + } + disabled={didConfirm} + interactive={!isReadOnly} + /> + )} {shouldShowBillable && ( {translate('common.billable')} @@ -835,5 +879,8 @@ export default compose( policy: { key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY}${policyID}`, }, + policyTaxRates: { + key: ({policyID}) => `${ONYXKEYS.COLLECTION.POLICY_TAX_RATE}${policyID}`, + }, }), )(MoneyTemporaryForRefactorRequestConfirmationList); diff --git a/src/components/MultipleAvatars.tsx b/src/components/MultipleAvatars.tsx index 23c795f281f6..a6f34cd459fc 100644 --- a/src/components/MultipleAvatars.tsx +++ b/src/components/MultipleAvatars.tsx @@ -1,10 +1,11 @@ import React, {memo, useMemo} from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; -import {ValueOf} from 'type-fest'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; +import type {ValueOf} from 'type-fest'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; -import {AvatarSource} from '@libs/UserUtils'; +import type {AvatarSource} from '@libs/UserUtils'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import type {Icon} from '@src/types/onyx/OnyxCommon'; @@ -142,7 +143,7 @@ function MultipleAvatars({ if (icons.length === 1 && !shouldStackHorizontally) { return ( ( {icons.length === 2 ? ( (props: BasePickerProps, ref: ForwardedRef) { return ( diff --git a/src/components/Picker/index.tsx b/src/components/Picker/index.tsx index 18184b130bba..591914b74d4c 100644 --- a/src/components/Picker/index.tsx +++ b/src/components/Picker/index.tsx @@ -1,4 +1,5 @@ -import React, {ForwardedRef, forwardRef} from 'react'; +import type {ForwardedRef} from 'react'; +import React, {forwardRef} from 'react'; import BasePicker from './BasePicker'; import type {AdditionalPickerEvents, BasePickerHandle, BasePickerProps, OnChange, OnMouseDown} from './types'; diff --git a/src/components/Picker/types.ts b/src/components/Picker/types.ts index 58eed0371893..edf39a59c9d8 100644 --- a/src/components/Picker/types.ts +++ b/src/components/Picker/types.ts @@ -1,5 +1,5 @@ -import {ChangeEvent, Component, ReactElement} from 'react'; -import {MeasureLayoutOnSuccessCallback, NativeMethods, StyleProp, ViewStyle} from 'react-native'; +import type {ChangeEvent, Component, ReactElement} from 'react'; +import type {MeasureLayoutOnSuccessCallback, NativeMethods, StyleProp, ViewStyle} from 'react-native'; type MeasureLayoutOnFailCallback = () => void; diff --git a/src/components/PlaidLink/index.native.tsx b/src/components/PlaidLink/index.native.tsx index c46a9df2076e..24ab75eb62b7 100644 --- a/src/components/PlaidLink/index.native.tsx +++ b/src/components/PlaidLink/index.native.tsx @@ -1,8 +1,9 @@ import {useEffect} from 'react'; -import {dismissLink, LinkEvent, openLink, usePlaidEmitter} from 'react-native-plaid-link-sdk'; +import type {LinkEvent} from 'react-native-plaid-link-sdk'; +import {dismissLink, openLink, usePlaidEmitter} from 'react-native-plaid-link-sdk'; import Log from '@libs/Log'; import CONST from '@src/CONST'; -import PlaidLinkProps from './types'; +import type PlaidLinkProps from './types'; function PlaidLink({token, onSuccess = () => {}, onExit = () => {}, onEvent}: PlaidLinkProps) { usePlaidEmitter((event: LinkEvent) => { diff --git a/src/components/PlaidLink/index.tsx b/src/components/PlaidLink/index.tsx index 08655458d12e..c3a913886f2c 100644 --- a/src/components/PlaidLink/index.tsx +++ b/src/components/PlaidLink/index.tsx @@ -1,10 +1,11 @@ import React, {useCallback, useEffect, useState} from 'react'; import {ActivityIndicator, View} from 'react-native'; -import {PlaidLinkOnSuccessMetadata, usePlaidLink} from 'react-plaid-link'; +import type {PlaidLinkOnSuccessMetadata} from 'react-plaid-link'; +import {usePlaidLink} from 'react-plaid-link'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; import Log from '@libs/Log'; -import PlaidLinkProps from './types'; +import type PlaidLinkProps from './types'; function PlaidLink({token, onSuccess = () => {}, onError = () => {}, onExit = () => {}, onEvent, receivedRedirectURI}: PlaidLinkProps) { const [isPlaidLoaded, setIsPlaidLoaded] = useState(false); diff --git a/src/components/PlaidLink/types.ts b/src/components/PlaidLink/types.ts index 1034eb935f74..48526520c736 100644 --- a/src/components/PlaidLink/types.ts +++ b/src/components/PlaidLink/types.ts @@ -1,5 +1,5 @@ -import {LinkEventMetadata, LinkSuccessMetadata} from 'react-native-plaid-link-sdk'; -import {PlaidLinkOnEventMetadata, PlaidLinkOnSuccessMetadata} from 'react-plaid-link'; +import type {LinkEventMetadata, LinkSuccessMetadata} from 'react-native-plaid-link-sdk'; +import type {PlaidLinkOnEventMetadata, PlaidLinkOnSuccessMetadata} from 'react-plaid-link'; type PlaidLinkProps = { // Plaid Link SDK public token used to initialize the Plaid SDK diff --git a/src/components/Popover/index.native.tsx b/src/components/Popover/index.native.tsx index 3fff04d00d2f..08ed15fd0d30 100644 --- a/src/components/Popover/index.native.tsx +++ b/src/components/Popover/index.native.tsx @@ -1,7 +1,7 @@ import React from 'react'; import Modal from '@components/Modal'; import CONST from '@src/CONST'; -import {PopoverProps} from './types'; +import type {PopoverProps} from './types'; /* * This is a convenience wrapper around the Modal component for a responsive Popover. diff --git a/src/components/Popover/index.tsx b/src/components/Popover/index.tsx index bf79415d4794..762e79fab63c 100644 --- a/src/components/Popover/index.tsx +++ b/src/components/Popover/index.tsx @@ -5,7 +5,7 @@ import {PopoverContext} from '@components/PopoverProvider'; import PopoverWithoutOverlay from '@components/PopoverWithoutOverlay'; import withWindowDimensions from '@components/withWindowDimensions'; import CONST from '@src/CONST'; -import {PopoverWithWindowDimensionsProps} from './types'; +import type {PopoverWithWindowDimensionsProps} from './types'; /* * This is a convenience wrapper around the Modal component for a responsive Popover. diff --git a/src/components/Popover/popoverPropTypes.js b/src/components/Popover/popoverPropTypes.js index c13fd8fa0b85..c758c4e6d311 100644 --- a/src/components/Popover/popoverPropTypes.js +++ b/src/components/Popover/popoverPropTypes.js @@ -26,6 +26,9 @@ const propTypes = { /** The ref of the popover */ withoutOverlayRef: refPropTypes, + + /** Whether we want to show the popover on the right side of the screen */ + fromSidebarMediumScreen: PropTypes.bool, }; const defaultProps = { diff --git a/src/components/Popover/types.ts b/src/components/Popover/types.ts index 7f7e2829770c..3d1f95822e6a 100644 --- a/src/components/Popover/types.ts +++ b/src/components/Popover/types.ts @@ -1,7 +1,16 @@ -import BaseModalProps, {PopoverAnchorPosition} from '@components/Modal/types'; -import {WindowDimensionsProps} from '@components/withWindowDimensions/types'; - -type AnchorAlignment = {horizontal: string; vertical: string}; +import type {ValueOf} from 'type-fest'; +import type {PopoverAnchorPosition} from '@components/Modal/types'; +import type BaseModalProps from '@components/Modal/types'; +import type {WindowDimensionsProps} from '@components/withWindowDimensions/types'; +import type CONST from '@src/CONST'; + +type AnchorAlignment = { + /** The horizontal anchor alignment of the popover */ + horizontal: ValueOf; + + /** The vertical anchor alignment of the popover */ + vertical: ValueOf; +}; type PopoverDimensions = { width: number; @@ -39,4 +48,4 @@ type PopoverProps = BaseModalProps & { type PopoverWithWindowDimensionsProps = PopoverProps & WindowDimensionsProps; -export type {PopoverProps, PopoverWithWindowDimensionsProps}; +export type {PopoverProps, PopoverWithWindowDimensionsProps, AnchorAlignment}; diff --git a/src/components/PopoverMenu.tsx b/src/components/PopoverMenu.tsx new file mode 100644 index 000000000000..502bdbf83b53 --- /dev/null +++ b/src/components/PopoverMenu.tsx @@ -0,0 +1,177 @@ +import type {ImageContentFit} from 'expo-image'; +import type {RefObject} from 'react'; +import React, {useRef} from 'react'; +import {View} from 'react-native'; +import type {ModalProps} from 'react-native-modal'; +import type {SvgProps} from 'react-native-svg'; +import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; +import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import CONST from '@src/CONST'; +import type {AnchorPosition} from '@src/styles'; +import MenuItem from './MenuItem'; +import type {AnchorAlignment} from './Popover/types'; +import PopoverWithMeasuredContent from './PopoverWithMeasuredContent'; +import Text from './Text'; + +type PopoverMenuItem = { + /** An icon element displayed on the left side */ + icon: React.FC; + + /** Text label */ + text: string; + + /** A callback triggered when this item is selected */ + onSelected: () => void; + + /** A description text to show under the title */ + description?: string; + + /** The fill color to pass into the icon. */ + iconFill?: string; + + /** Icon Width */ + iconWidth?: number; + + /** Icon Height */ + iconHeight?: number; + + /** Icon should be displayed in its own color */ + displayInDefaultIconColor?: boolean; + + /** Determines how the icon should be resized to fit its container */ + contentFit?: ImageContentFit; +}; + +type PopoverModalProps = Pick; + +type PopoverMenuProps = PopoverModalProps & { + /** Callback method fired when the user requests to close the modal */ + onClose: () => void; + + /** State that determines whether to display the modal or not */ + isVisible: boolean; + + /** Callback to fire when a CreateMenu item is selected */ + onItemSelected: (selectedItem: PopoverMenuItem, index: number) => void; + + /** Menu items to be rendered on the list */ + menuItems: PopoverMenuItem[]; + + /** Optional non-interactive text to display as a header for any create menu */ + headerText?: string; + + /** Whether disable the animations */ + disableAnimation?: boolean; + + /** The horizontal and vertical anchors points for the popover */ + anchorPosition: AnchorPosition; + + /** Ref of the anchor */ + anchorRef: RefObject; + + /** Where the popover should be positioned relative to the anchor points. */ + anchorAlignment?: AnchorAlignment; + + /** Whether we don't want to show overlay */ + withoutOverlay?: boolean; + + /** Should we announce the Modal visibility changes? */ + shouldSetModalVisibility?: boolean; + + /** Whether we want to show the popover on the right side of the screen */ + fromSidebarMediumScreen?: boolean; +}; + +function PopoverMenu({ + menuItems, + onItemSelected, + isVisible, + anchorPosition, + anchorRef, + onClose, + headerText, + fromSidebarMediumScreen, + anchorAlignment = { + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, + }, + animationIn = 'fadeIn', + animationOut = 'fadeOut', + animationInTiming = CONST.ANIMATED_TRANSITION, + disableAnimation = true, + withoutOverlay = false, + shouldSetModalVisibility = true, +}: PopoverMenuProps) { + const styles = useThemeStyles(); + const {isSmallScreenWidth} = useWindowDimensions(); + const selectedItemIndex = useRef(null); + const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({initialFocusedIndex: -1, maxIndex: menuItems.length - 1, isActive: isVisible}); + + const selectItem = (index: number) => { + const selectedItem = menuItems[index]; + onItemSelected(selectedItem, index); + selectedItemIndex.current = index; + }; + + useKeyboardShortcut( + CONST.KEYBOARD_SHORTCUTS.ENTER, + () => { + if (focusedIndex === -1) { + return; + } + selectItem(focusedIndex); + setFocusedIndex(-1); // Reset the focusedIndex on selecting any menu + }, + {isActive: isVisible}, + ); + + return ( + { + setFocusedIndex(-1); + if (selectedItemIndex.current !== null) { + menuItems[selectedItemIndex.current].onSelected(); + selectedItemIndex.current = null; + } + }} + animationIn={animationIn} + animationOut={animationOut} + animationInTiming={animationInTiming} + disableAnimation={disableAnimation} + fromSidebarMediumScreen={fromSidebarMediumScreen} + withoutOverlay={withoutOverlay} + shouldSetModalVisibility={shouldSetModalVisibility} + > + + {!!headerText && {headerText}} + {menuItems.map((item, menuIndex) => ( + selectItem(menuIndex)} + focused={focusedIndex === menuIndex} + displayInDefaultIconColor={item.displayInDefaultIconColor} + /> + ))} + + + ); +} + +PopoverMenu.displayName = 'PopoverMenu'; + +export default React.memo(PopoverMenu); diff --git a/src/components/PopoverMenu/index.js b/src/components/PopoverMenu/index.js deleted file mode 100644 index 597105173b4c..000000000000 --- a/src/components/PopoverMenu/index.js +++ /dev/null @@ -1,114 +0,0 @@ -import PropTypes from 'prop-types'; -import React, {useRef} from 'react'; -import {View} from 'react-native'; -import _ from 'underscore'; -import MenuItem from '@components/MenuItem'; -import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; -import refPropTypes from '@components/refPropTypes'; -import Text from '@components/Text'; -import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; -import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; -import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; -import useThemeStyles from '@hooks/useThemeStyles'; -import useWindowDimensions from '@hooks/useWindowDimensions'; -import CONST from '@src/CONST'; -import {defaultProps as createMenuDefaultProps, propTypes as createMenuPropTypes} from './popoverMenuPropTypes'; - -const propTypes = { - ...createMenuPropTypes, - ...windowDimensionsPropTypes, - - /** Ref of the anchor */ - anchorRef: refPropTypes, - - withoutOverlay: PropTypes.bool, - - /** Should we announce the Modal visibility changes? */ - shouldSetModalVisibility: PropTypes.bool, -}; - -const defaultProps = { - ...createMenuDefaultProps, - anchorAlignment: { - horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, - vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, - }, - anchorRef: () => {}, - withoutOverlay: false, - shouldSetModalVisibility: true, -}; - -function PopoverMenu(props) { - const styles = useThemeStyles(); - const {isSmallScreenWidth} = useWindowDimensions(); - const selectedItemIndex = useRef(null); - const [focusedIndex, setFocusedIndex] = useArrowKeyFocusManager({initialFocusedIndex: -1, maxIndex: props.menuItems.length - 1, isActive: props.isVisible}); - - const selectItem = (index) => { - const selectedItem = props.menuItems[index]; - props.onItemSelected(selectedItem, index); - selectedItemIndex.current = index; - }; - - useKeyboardShortcut( - CONST.KEYBOARD_SHORTCUTS.ENTER, - () => { - if (focusedIndex === -1) { - return; - } - selectItem(focusedIndex); - setFocusedIndex(-1); // Reset the focusedIndex on selecting any menu - }, - {isActive: props.isVisible}, - ); - - return ( - { - setFocusedIndex(-1); - if (selectedItemIndex.current !== null) { - props.menuItems[selectedItemIndex.current].onSelected(); - selectedItemIndex.current = null; - } - }} - animationIn={props.animationIn} - animationOut={props.animationOut} - animationInTiming={props.animationInTiming} - disableAnimation={props.disableAnimation} - fromSidebarMediumScreen={props.fromSidebarMediumScreen} - withoutOverlay={props.withoutOverlay} - shouldSetModalVisibility={props.shouldSetModalVisibility} - > - - {!_.isEmpty(props.headerText) && {props.headerText}} - {_.map(props.menuItems, (item, menuIndex) => ( - selectItem(menuIndex)} - focused={focusedIndex === menuIndex} - displayInDefaultIconColor={item.displayInDefaultIconColor} - /> - ))} - - - ); -} - -PopoverMenu.propTypes = propTypes; -PopoverMenu.defaultProps = defaultProps; -PopoverMenu.displayName = 'PopoverMenu'; - -export default React.memo(withWindowDimensions(PopoverMenu)); diff --git a/src/components/PopoverMenu/popoverMenuPropTypes.js b/src/components/PopoverMenu/popoverMenuPropTypes.js deleted file mode 100644 index 53eeb63b05e7..000000000000 --- a/src/components/PopoverMenu/popoverMenuPropTypes.js +++ /dev/null @@ -1,71 +0,0 @@ -import PropTypes from 'prop-types'; -import _ from 'underscore'; -import sourcePropTypes from '@components/Image/sourcePropTypes'; -import CONST from '@src/CONST'; - -const propTypes = { - /** Callback method fired when the user requests to close the modal */ - onClose: PropTypes.func.isRequired, - - /** State that determines whether to display the modal or not */ - isVisible: PropTypes.bool.isRequired, - - /** Callback to fire when a CreateMenu item is selected */ - onItemSelected: PropTypes.func.isRequired, - - /** Menu items to be rendered on the list */ - menuItems: PropTypes.arrayOf( - PropTypes.shape({ - /** An icon element displayed on the left side */ - icon: sourcePropTypes, - - /** Text label */ - text: PropTypes.string.isRequired, - - /** A callback triggered when this item is selected */ - onSelected: PropTypes.func.isRequired, - }), - ).isRequired, - - /** The anchor position of the CreateMenu popover */ - anchorPosition: PropTypes.shape({ - top: PropTypes.number, - right: PropTypes.number, - bottom: PropTypes.number, - left: PropTypes.number, - }).isRequired, - - /** Where the popover should be positioned relative to the anchor points. */ - anchorAlignment: PropTypes.shape({ - horizontal: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL)), - vertical: PropTypes.oneOf(_.values(CONST.MODAL.ANCHOR_ORIGIN_VERTICAL)), - }), - - /** The anchor reference of the CreateMenu popover */ - anchorRef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]).isRequired, - - /** A react-native-animatable animation definition for the modal display animation. */ - animationIn: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - - /** A react-native-animatable animation definition for the modal hide animation. */ - animationOut: PropTypes.oneOfType([PropTypes.string, PropTypes.object]), - - /** A react-native-animatable animation timing for the modal display animation. */ - animationInTiming: PropTypes.number, - - /** Optional non-interactive text to display as a header for any create menu */ - headerText: PropTypes.string, - - /** Whether disable the animations */ - disableAnimation: PropTypes.bool, -}; - -const defaultProps = { - animationIn: 'fadeIn', - animationOut: 'fadeOut', - animationInTiming: CONST.ANIMATED_TRANSITION, - headerText: undefined, - disableAnimation: true, -}; - -export {propTypes, defaultProps}; diff --git a/src/components/PopoverProvider/index.native.tsx b/src/components/PopoverProvider/index.native.tsx index a87036c61808..b13909945bef 100644 --- a/src/components/PopoverProvider/index.native.tsx +++ b/src/components/PopoverProvider/index.native.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {PopoverContextProps, PopoverContextValue} from './types'; +import type {PopoverContextProps, PopoverContextValue} from './types'; const PopoverContext = React.createContext({ onOpen: () => {}, diff --git a/src/components/PopoverProvider/index.tsx b/src/components/PopoverProvider/index.tsx index 06345ebdbc1c..b50b04289813 100644 --- a/src/components/PopoverProvider/index.tsx +++ b/src/components/PopoverProvider/index.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import {AnchorRef, PopoverContextProps, PopoverContextValue} from './types'; +import type {AnchorRef, PopoverContextProps, PopoverContextValue} from './types'; const PopoverContext = React.createContext({ onOpen: () => {}, diff --git a/src/components/PopoverWithMeasuredContent.tsx b/src/components/PopoverWithMeasuredContent.tsx index 9d10f7869f8a..792002441ac6 100644 --- a/src/components/PopoverWithMeasuredContent.tsx +++ b/src/components/PopoverWithMeasuredContent.tsx @@ -1,15 +1,17 @@ import isEqual from 'lodash/isEqual'; import React, {useMemo, useState} from 'react'; -import {LayoutChangeEvent, View} from 'react-native'; +import type {LayoutChangeEvent} from 'react-native'; +import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import PopoverWithMeasuredContentUtils from '@libs/PopoverWithMeasuredContentUtils'; import CONST from '@src/CONST'; import type {AnchorPosition} from '@src/styles'; import Popover from './Popover'; -import {PopoverProps} from './Popover/types'; +import type {PopoverProps} from './Popover/types'; +import type {WindowDimensionsProps} from './withWindowDimensions/types'; -type PopoverWithMeasuredContentProps = Omit & { +type PopoverWithMeasuredContentProps = Omit & { /** The horizontal and vertical anchors points for the popover */ anchorPosition: AnchorPosition; }; @@ -34,6 +36,12 @@ function PopoverWithMeasuredContent({ }, children, withoutOverlay = false, + fullscreen = true, + shouldCloseOnOutsideClick = false, + shouldSetModalVisibility = true, + statusBarTranslucent = true, + avoidKeyboard = false, + hideModalContentWhileAnimating = false, ...props }: PopoverWithMeasuredContentProps) { const styles = useThemeStyles(); @@ -113,6 +121,12 @@ function PopoverWithMeasuredContent({ anchorAlignment={anchorAlignment} isVisible={isVisible} withoutOverlay={withoutOverlay} + fullscreen={fullscreen} + shouldCloseOnOutsideClick={shouldCloseOnOutsideClick} + shouldSetModalVisibility={shouldSetModalVisibility} + statusBarTranslucent={statusBarTranslucent} + avoidKeyboard={avoidKeyboard} + hideModalContentWhileAnimating={hideModalContentWhileAnimating} // eslint-disable-next-line react/jsx-props-no-spreading {...props} anchorPosition={shiftedAnchorPosition} diff --git a/src/components/PopoverWithoutOverlay/index.tsx b/src/components/PopoverWithoutOverlay/index.tsx index f83949bcbe9d..6aed275bd2dc 100644 --- a/src/components/PopoverWithoutOverlay/index.tsx +++ b/src/components/PopoverWithoutOverlay/index.tsx @@ -1,4 +1,5 @@ -import React, {ForwardedRef, forwardRef, useContext, useEffect, useMemo} from 'react'; +import type {ForwardedRef} from 'react'; +import React, {forwardRef, useContext, useEffect, useMemo} from 'react'; import {View} from 'react-native'; import ColorSchemeWrapper from '@components/ColorSchemeWrapper'; import {PopoverContext} from '@components/PopoverProvider'; @@ -7,7 +8,7 @@ import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as Modal from '@userActions/Modal'; -import PopoverWithoutOverlayProps from './types'; +import type PopoverWithoutOverlayProps from './types'; function PopoverWithoutOverlay( { diff --git a/src/components/PopoverWithoutOverlay/types.ts b/src/components/PopoverWithoutOverlay/types.ts index 0d70c4a5facb..ff4f73fd4114 100644 --- a/src/components/PopoverWithoutOverlay/types.ts +++ b/src/components/PopoverWithoutOverlay/types.ts @@ -1,6 +1,6 @@ -import {View} from 'react-native'; -import BaseModalProps from '@components/Modal/types'; -import ChildrenProps from '@src/types/utils/ChildrenProps'; +import type {View} from 'react-native'; +import type BaseModalProps from '@components/Modal/types'; +import type ChildrenProps from '@src/types/utils/ChildrenProps'; type PopoverWithoutOverlayProps = ChildrenProps & Omit & { diff --git a/src/components/Pressable/GenericPressable/BaseGenericPressable.tsx b/src/components/Pressable/GenericPressable/BaseGenericPressable.tsx index 0a9f544bcee1..f41a6b389001 100644 --- a/src/components/Pressable/GenericPressable/BaseGenericPressable.tsx +++ b/src/components/Pressable/GenericPressable/BaseGenericPressable.tsx @@ -1,6 +1,8 @@ -import React, {ForwardedRef, forwardRef, useCallback, useEffect, useMemo} from 'react'; +import type {ForwardedRef} from 'react'; +import React, {forwardRef, useCallback, useEffect, useMemo} from 'react'; +import type {GestureResponderEvent, View} from 'react-native'; // eslint-disable-next-line no-restricted-imports -import {GestureResponderEvent, Pressable, View} from 'react-native'; +import {Pressable} from 'react-native'; import useSingleExecution from '@hooks/useSingleExecution'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; @@ -8,7 +10,8 @@ import Accessibility from '@libs/Accessibility'; import HapticFeedback from '@libs/HapticFeedback'; import KeyboardShortcut from '@libs/KeyboardShortcut'; import CONST from '@src/CONST'; -import PressableProps, {PressableRef} from './types'; +import type {PressableRef} from './types'; +import type PressableProps from './types'; function GenericPressable( { diff --git a/src/components/Pressable/GenericPressable/index.native.tsx b/src/components/Pressable/GenericPressable/index.native.tsx index 968ee6063a95..c17163677cbe 100644 --- a/src/components/Pressable/GenericPressable/index.native.tsx +++ b/src/components/Pressable/GenericPressable/index.native.tsx @@ -1,6 +1,7 @@ import React, {forwardRef} from 'react'; import GenericPressable from './BaseGenericPressable'; -import PressableProps, {PressableRef} from './types'; +import type {PressableRef} from './types'; +import type PressableProps from './types'; function NativeGenericPressable(props: PressableProps, ref: PressableRef) { return ( diff --git a/src/components/Pressable/GenericPressable/index.tsx b/src/components/Pressable/GenericPressable/index.tsx index 51099733e04f..3d6301379155 100644 --- a/src/components/Pressable/GenericPressable/index.tsx +++ b/src/components/Pressable/GenericPressable/index.tsx @@ -1,7 +1,8 @@ import React, {forwardRef} from 'react'; -import {Role} from 'react-native'; +import type {Role} from 'react-native'; import GenericPressable from './BaseGenericPressable'; -import PressableProps, {PressableRef} from './types'; +import type {PressableRef} from './types'; +import type PressableProps from './types'; function WebGenericPressable({focusable = true, ...props}: PressableProps, ref: PressableRef) { const accessible = props.accessible ?? props.accessible === undefined ? true : props.accessible; diff --git a/src/components/Pressable/GenericPressable/types.ts b/src/components/Pressable/GenericPressable/types.ts index cdb9a8624114..dc04b6fcf329 100644 --- a/src/components/Pressable/GenericPressable/types.ts +++ b/src/components/Pressable/GenericPressable/types.ts @@ -1,8 +1,8 @@ -import {ElementRef, ForwardedRef, RefObject} from 'react'; -import {GestureResponderEvent, HostComponent, PressableStateCallbackType, PressableProps as RNPressableProps, StyleProp, View, ViewStyle} from 'react-native'; -import {ValueOf} from 'type-fest'; -import {Shortcut} from '@libs/KeyboardShortcut'; -import CONST from '@src/CONST'; +import type {ElementRef, ForwardedRef, RefObject} from 'react'; +import type {GestureResponderEvent, HostComponent, PressableStateCallbackType, PressableProps as RNPressableProps, StyleProp, View, ViewStyle} from 'react-native'; +import type {ValueOf} from 'type-fest'; +import type {Shortcut} from '@libs/KeyboardShortcut'; +import type CONST from '@src/CONST'; type StylePropWithFunction = StyleProp | ((state: PressableStateCallbackType) => StyleProp); diff --git a/src/components/Pressable/PressableWithDelayToggle.tsx b/src/components/Pressable/PressableWithDelayToggle.tsx index d265be929b0c..ab1fa95efeb5 100644 --- a/src/components/Pressable/PressableWithDelayToggle.tsx +++ b/src/components/Pressable/PressableWithDelayToggle.tsx @@ -1,6 +1,6 @@ /* eslint-disable react-native-a11y/has-valid-accessibility-descriptors */ import React, {forwardRef} from 'react'; -import {StyleProp, TextStyle, ViewStyle} from 'react-native'; +import type {StyleProp, TextStyle, ViewStyle} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import Text from '@components/Text'; @@ -10,8 +10,9 @@ import useThemeStyles from '@hooks/useThemeStyles'; import useThrottledButtonState from '@hooks/useThrottledButtonState'; import getButtonState from '@libs/getButtonState'; import variables from '@styles/variables'; -import IconAsset from '@src/types/utils/IconAsset'; -import PressableProps, {PressableRef} from './GenericPressable/types'; +import type IconAsset from '@src/types/utils/IconAsset'; +import type {PressableRef} from './GenericPressable/types'; +import type PressableProps from './GenericPressable/types'; import PressableWithoutFeedback from './PressableWithoutFeedback'; type PressableWithDelayToggleProps = PressableProps & { diff --git a/src/components/Pressable/PressableWithFeedback.tsx b/src/components/Pressable/PressableWithFeedback.tsx index a4c439c4441c..b717c4890a2d 100644 --- a/src/components/Pressable/PressableWithFeedback.tsx +++ b/src/components/Pressable/PressableWithFeedback.tsx @@ -1,10 +1,11 @@ import React, {forwardRef, useState} from 'react'; -import {StyleProp, ViewStyle} from 'react-native'; -import {AnimatedStyle} from 'react-native-reanimated'; +import type {StyleProp, ViewStyle} from 'react-native'; +import type {AnimatedStyle} from 'react-native-reanimated'; import OpacityView from '@components/OpacityView'; import variables from '@styles/variables'; import GenericPressable from './GenericPressable'; -import PressableProps, {PressableRef} from './GenericPressable/types'; +import type {PressableRef} from './GenericPressable/types'; +import type PressableProps from './GenericPressable/types'; type PressableWithFeedbackProps = PressableProps & { /** Style for the wrapper view */ diff --git a/src/components/Pressable/PressableWithoutFeedback.tsx b/src/components/Pressable/PressableWithoutFeedback.tsx index fd9d695cc2ed..f95050dd649e 100644 --- a/src/components/Pressable/PressableWithoutFeedback.tsx +++ b/src/components/Pressable/PressableWithoutFeedback.tsx @@ -1,6 +1,7 @@ import React from 'react'; import GenericPressable from './GenericPressable'; -import PressableProps, {PressableRef} from './GenericPressable/types'; +import type {PressableRef} from './GenericPressable/types'; +import type PressableProps from './GenericPressable/types'; function PressableWithoutFeedback( {pressStyle, hoverStyle, focusStyle, disabledStyle, screenReaderActiveStyle, shouldUseHapticsOnPress, shouldUseHapticsOnLongPress, ...rest}: PressableProps, diff --git a/src/components/Pressable/PressableWithoutFocus.tsx b/src/components/Pressable/PressableWithoutFocus.tsx index 32cb1708baf0..f887b0ea9b7d 100644 --- a/src/components/Pressable/PressableWithoutFocus.tsx +++ b/src/components/Pressable/PressableWithoutFocus.tsx @@ -1,7 +1,7 @@ import React, {useRef} from 'react'; -import {View} from 'react-native'; +import type {View} from 'react-native'; import GenericPressable from './GenericPressable'; -import PressableProps from './GenericPressable/types'; +import type PressableProps from './GenericPressable/types'; /** * This component prevents the tapped element from capturing focus. diff --git a/src/components/PressableWithSecondaryInteraction/index.native.tsx b/src/components/PressableWithSecondaryInteraction/index.native.tsx index 77dc9452f986..d448e6d6ecc3 100644 --- a/src/components/PressableWithSecondaryInteraction/index.native.tsx +++ b/src/components/PressableWithSecondaryInteraction/index.native.tsx @@ -1,9 +1,10 @@ -import React, {forwardRef, ReactNode} from 'react'; -import {GestureResponderEvent, TextProps} from 'react-native'; -import {PressableRef} from '@components/Pressable/GenericPressable/types'; +import type {ReactNode} from 'react'; +import React, {forwardRef} from 'react'; +import type {GestureResponderEvent, TextProps} from 'react-native'; +import type {PressableRef} from '@components/Pressable/GenericPressable/types'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Text from '@components/Text'; -import PressableWithSecondaryInteractionProps from './types'; +import type PressableWithSecondaryInteractionProps from './types'; /** This is a special Pressable that calls onSecondaryInteraction when LongPressed. */ function PressableWithSecondaryInteraction( diff --git a/src/components/PressableWithSecondaryInteraction/index.tsx b/src/components/PressableWithSecondaryInteraction/index.tsx index 0cb53e40325a..5e2de765f733 100644 --- a/src/components/PressableWithSecondaryInteraction/index.tsx +++ b/src/components/PressableWithSecondaryInteraction/index.tsx @@ -1,11 +1,11 @@ import React, {forwardRef, useEffect, useRef} from 'react'; -import {GestureResponderEvent} from 'react-native'; -import {PressableRef} from '@components/Pressable/GenericPressable/types'; +import type {GestureResponderEvent} from 'react-native'; +import type {PressableRef} from '@components/Pressable/GenericPressable/types'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import PressableWithSecondaryInteractionProps from './types'; +import type PressableWithSecondaryInteractionProps from './types'; /** This is a special Pressable that calls onSecondaryInteraction when LongPressed, or right-clicked. */ function PressableWithSecondaryInteraction( diff --git a/src/components/PressableWithSecondaryInteraction/types.ts b/src/components/PressableWithSecondaryInteraction/types.ts index bf999e9692b5..aa67d45d66fb 100644 --- a/src/components/PressableWithSecondaryInteraction/types.ts +++ b/src/components/PressableWithSecondaryInteraction/types.ts @@ -1,5 +1,5 @@ import type {GestureResponderEvent} from 'react-native'; -import {PressableWithFeedbackProps} from '@components/Pressable/PressableWithFeedback'; +import type {PressableWithFeedbackProps} from '@components/Pressable/PressableWithFeedback'; import type {ParsableStyle} from '@styles/utils/types'; type PressableWithSecondaryInteractionProps = PressableWithFeedbackProps & { diff --git a/src/components/QRCode.tsx b/src/components/QRCode.tsx index 99a3a1f64118..89c367bab222 100644 --- a/src/components/QRCode.tsx +++ b/src/components/QRCode.tsx @@ -1,5 +1,6 @@ -import React, {Ref} from 'react'; -import {ImageSourcePropType} from 'react-native'; +import type {Ref} from 'react'; +import React from 'react'; +import type {ImageSourcePropType} from 'react-native'; import QRCodeLibrary from 'react-native-qrcode-svg'; import useTheme from '@hooks/useTheme'; import CONST from '@src/CONST'; diff --git a/src/components/RNTextInput.tsx b/src/components/RNTextInput.tsx index 40dfc109ba6b..f7917a852704 100644 --- a/src/components/RNTextInput.tsx +++ b/src/components/RNTextInput.tsx @@ -1,7 +1,10 @@ -import React, {Component, ForwardedRef} from 'react'; +import type {Component, ForwardedRef} from 'react'; +import React from 'react'; // eslint-disable-next-line no-restricted-imports -import {TextInput, TextInputProps} from 'react-native'; -import Animated, {AnimatedProps} from 'react-native-reanimated'; +import type {TextInputProps} from 'react-native'; +import {TextInput} from 'react-native'; +import type {AnimatedProps} from 'react-native-reanimated'; +import Animated from 'react-native-reanimated'; import useTheme from '@hooks/useTheme'; type AnimatedTextInputRef = Component>; diff --git a/src/components/RadioButtonWithLabel.tsx b/src/components/RadioButtonWithLabel.tsx index 4c223262ac50..f4bad4c082a7 100644 --- a/src/components/RadioButtonWithLabel.tsx +++ b/src/components/RadioButtonWithLabel.tsx @@ -1,5 +1,7 @@ -import React, {ComponentType} from 'react'; -import {StyleProp, View, ViewStyle} from 'react-native'; +import type {ComponentType} from 'react'; +import React from 'react'; +import type {StyleProp, ViewStyle} from 'react-native'; +import {View} from 'react-native'; import useThemeStyles from '@hooks/useThemeStyles'; import FormHelpMessage from './FormHelpMessage'; import * as Pressables from './Pressable'; diff --git a/src/components/Reactions/AddReactionBubble.js b/src/components/Reactions/AddReactionBubble.tsx similarity index 62% rename from src/components/Reactions/AddReactionBubble.js rename to src/components/Reactions/AddReactionBubble.tsx index a9bfdd367615..52751368a0ae 100644 --- a/src/components/Reactions/AddReactionBubble.js +++ b/src/components/Reactions/AddReactionBubble.tsx @@ -1,81 +1,75 @@ -import PropTypes from 'prop-types'; import React, {useEffect, useRef} from 'react'; import {View} from 'react-native'; +import type {Emoji} from '@assets/emojis/types'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip/PopoverAnchorTooltip'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; import variables from '@styles/variables'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; +import type {AnchorOrigin} from '@userActions/EmojiPickerAction'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; +import type {ReportAction} from '@src/types/onyx'; +import type {CloseContextMenuCallback, OpenPickerCallback, PickerRefElement} from './QuickEmojiReactions/types'; -const propTypes = { +type AddReactionBubbleProps = { /** Whether it is for context menu so we can modify its style */ - isContextMenu: PropTypes.bool, + isContextMenu?: boolean; /** * Called when the user presses on the icon button. * Will have a function as parameter which you can call * to open the picker. */ - onPressOpenPicker: PropTypes.func, + onPressOpenPicker?: (openPicker: OpenPickerCallback) => void; /** * Will get called the moment before the picker opens. */ - onWillShowPicker: PropTypes.func, + onWillShowPicker?: (callback: CloseContextMenuCallback) => void; /** * Called when the user selects an emoji. */ - onSelectEmoji: PropTypes.func.isRequired, + onSelectEmoji: (emoji: Emoji) => void; /** * ReportAction for EmojiPicker. */ - reportAction: PropTypes.shape({ - reportActionID: PropTypes.string.isRequired, - }), - - ...withLocalizePropTypes, -}; - -const defaultProps = { - isContextMenu: false, - onWillShowPicker: () => {}, - onPressOpenPicker: undefined, - reportAction: {}, + reportAction: ReportAction; }; -function AddReactionBubble(props) { +function AddReactionBubble({onSelectEmoji, reportAction, onPressOpenPicker, onWillShowPicker = () => {}, isContextMenu = false}: AddReactionBubbleProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const ref = useRef(); + const ref = useRef(null); + const {translate} = useLocalize(); + useEffect(() => EmojiPickerAction.resetEmojiPopoverAnchor, []); const onPress = () => { - const openPicker = (refParam, anchorOrigin) => { + const openPicker = (refParam?: PickerRefElement, anchorOrigin?: AnchorOrigin) => { EmojiPickerAction.showEmojiPicker( () => {}, (emojiCode, emojiObject) => { - props.onSelectEmoji(emojiObject); + onSelectEmoji(emojiObject); }, - refParam || ref, + refParam ?? ref, anchorOrigin, - props.onWillShowPicker, - props.reportAction.reportActionID, + onWillShowPicker, + reportAction.reportActionID, ); }; - if (!EmojiPickerAction.emojiPickerRef.current.isEmojiPickerVisible) { - if (props.onPressOpenPicker) { - props.onPressOpenPicker(openPicker); + if (!EmojiPickerAction.emojiPickerRef.current?.isEmojiPickerVisible) { + if (onPressOpenPicker) { + onPressOpenPicker(openPicker); } else { openPicker(); } @@ -85,21 +79,21 @@ function AddReactionBubble(props) { }; return ( - + [styles.emojiReactionBubble, styles.userSelectNone, StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, false, props.isContextMenu)]} + style={({hovered, pressed}) => [styles.emojiReactionBubble, styles.userSelectNone, StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, false, isContextMenu)]} onPress={Session.checkIfActionIsAllowed(onPress)} - onMouseDown={(e) => { + onMouseDown={(event) => { // Allow text input blur when Add reaction is right clicked - if (!e || e.button === 2) { + if (!event || event.button === 2) { return; } // Prevent text input blur when Add reaction is left clicked - e.preventDefault(); + event.preventDefault(); }} - accessibilityLabel={props.translate('emojiReactions.addReactionTooltip')} + accessibilityLabel={translate('emojiReactions.addReactionTooltip')} role={CONST.ROLE.BUTTON} // disable dimming pressDimmingValue={1} @@ -110,12 +104,12 @@ function AddReactionBubble(props) { {/* This (invisible) text will make the view have the same size as a regular emoji reaction. We make the text invisible and put the icon on top of it. */} - {'\u2800\u2800'} + {'\u2800\u2800'} @@ -126,8 +120,6 @@ function AddReactionBubble(props) { ); } -AddReactionBubble.propTypes = propTypes; -AddReactionBubble.defaultProps = defaultProps; AddReactionBubble.displayName = 'AddReactionBubble'; -export default withLocalize(AddReactionBubble); +export default AddReactionBubble; diff --git a/src/components/Reactions/EmojiReactionBubble.js b/src/components/Reactions/EmojiReactionBubble.js deleted file mode 100644 index 3fd22a758f67..000000000000 --- a/src/components/Reactions/EmojiReactionBubble.js +++ /dev/null @@ -1,110 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; -import Text from '@components/Text'; -import {withCurrentUserPersonalDetailsDefaultProps} from '@components/withCurrentUserPersonalDetails'; -import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; -import useStyleUtils from '@hooks/useStyleUtils'; -import useThemeStyles from '@hooks/useThemeStyles'; -import CONST from '@src/CONST'; - -const propTypes = { - /** - * The emoji codes to display in the bubble. - */ - emojiCodes: PropTypes.arrayOf(PropTypes.string).isRequired, - - /** - * Called when the user presses on the reaction bubble. - */ - onPress: PropTypes.func.isRequired, - - /** - * Called when the user long presses or right clicks - * on the reaction bubble. - */ - onReactionListOpen: PropTypes.func, - - /** - * The number of reactions to display in the bubble. - */ - count: PropTypes.number, - - /** Whether it is for context menu so we can modify its style */ - isContextMenu: PropTypes.bool, - - /** - * Returns true if the current account has reacted to the report action (with the given skin tone). - */ - hasUserReacted: PropTypes.bool, - - /** We disable reacting with emojis on report actions that have errors */ - shouldBlockReactions: PropTypes.bool, - - ...windowDimensionsPropTypes, -}; - -const defaultProps = { - count: 0, - onReactionListOpen: () => {}, - isContextMenu: false, - shouldBlockReactions: false, - - ...withCurrentUserPersonalDetailsDefaultProps, -}; - -function EmojiReactionBubble(props) { - const styles = useThemeStyles(); - const StyleUtils = useStyleUtils(); - return ( - [ - styles.emojiReactionBubble, - StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, props.hasUserReacted, props.isContextMenu), - props.shouldBlockReactions && styles.cursorDisabled, - styles.userSelectNone, - ]} - onPress={() => { - if (props.shouldBlockReactions) { - return; - } - - props.onPress(); - }} - onSecondaryInteraction={props.onReactionListOpen} - ref={props.forwardedRef} - enableLongPressWithHover={props.isSmallScreenWidth} - onMouseDown={(e) => { - // Allow text input blur when emoji reaction is right clicked - if (e && e.button === 2) { - return; - } - - // Prevent text input blur when emoji reaction is left clicked - e.preventDefault(); - }} - role={CONST.ROLE.BUTTON} - accessibilityLabel={props.emojiCodes.join('')} - dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} - > - {props.emojiCodes.join('')} - {props.count > 0 && {props.count}} - - ); -} - -EmojiReactionBubble.propTypes = propTypes; -EmojiReactionBubble.defaultProps = defaultProps; -EmojiReactionBubble.displayName = 'EmojiReactionBubble'; - -const EmojiReactionBubbleWithRef = React.forwardRef((props, ref) => ( - -)); - -EmojiReactionBubbleWithRef.displayName = 'EmojiReactionBubbleWithRef'; - -export default withWindowDimensions(EmojiReactionBubbleWithRef); diff --git a/src/components/Reactions/EmojiReactionBubble.tsx b/src/components/Reactions/EmojiReactionBubble.tsx new file mode 100644 index 000000000000..d83689de2dc1 --- /dev/null +++ b/src/components/Reactions/EmojiReactionBubble.tsx @@ -0,0 +1,93 @@ +import React from 'react'; +import type {PressableRef} from '@components/Pressable/GenericPressable/types'; +import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; +import Text from '@components/Text'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; +import useWindowDimensions from '@hooks/useWindowDimensions'; +import type {ReactionListEvent} from '@pages/home/ReportScreenContext'; +import CONST from '@src/CONST'; + +type EmojiReactionBubbleProps = { + /** + * The emoji codes to display in the bubble. + */ + emojiCodes: string[]; + + /** + * Called when the user presses on the reaction bubble. + */ + onPress: () => void; + + /** + * Called when the user long presses or right clicks + * on the reaction bubble. + */ + onReactionListOpen?: (event: ReactionListEvent) => void; + + /** + * The number of reactions to display in the bubble. + */ + count?: number; + + /** Whether it is for context menu so we can modify its style */ + isContextMenu?: boolean; + + /** + * Returns true if the current account has reacted to the report action (with the given skin tone). + */ + hasUserReacted?: boolean; + + /** We disable reacting with emojis on report actions that have errors */ + shouldBlockReactions?: boolean; +}; + +function EmojiReactionBubble( + {onPress, onReactionListOpen = () => {}, emojiCodes, hasUserReacted = false, count = 0, isContextMenu = false, shouldBlockReactions = false}: EmojiReactionBubbleProps, + ref: PressableRef, +) { + const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); + const {isSmallScreenWidth} = useWindowDimensions(); + + return ( + [ + styles.emojiReactionBubble, + StyleUtils.getEmojiReactionBubbleStyle(hovered || pressed, hasUserReacted, isContextMenu), + shouldBlockReactions && styles.cursorDisabled, + styles.userSelectNone, + ]} + onPress={() => { + if (shouldBlockReactions) { + return; + } + + onPress(); + }} + onSecondaryInteraction={onReactionListOpen} + ref={ref} + enableLongPressWithHover={isSmallScreenWidth} + onMouseDown={(event) => { + // Allow text input blur when emoji reaction is right clicked + if (event?.button === 2) { + return; + } + + // Prevent text input blur when emoji reaction is left clicked + event.preventDefault(); + }} + role={CONST.ROLE.BUTTON} + accessibilityLabel={emojiCodes.join('')} + accessible + dataSet={{[CONST.SELECTION_SCRAPER_HIDDEN_ELEMENT]: true}} + > + {emojiCodes.join('')} + {count > 0 && {count}} + + ); +} + +EmojiReactionBubble.displayName = 'EmojiReactionBubble'; + +export default React.forwardRef(EmojiReactionBubble); diff --git a/src/components/Reactions/MiniQuickEmojiReactions.js b/src/components/Reactions/MiniQuickEmojiReactions.tsx similarity index 56% rename from src/components/Reactions/MiniQuickEmojiReactions.js rename to src/components/Reactions/MiniQuickEmojiReactions.tsx index 376afcb9ade5..9f38da6bdb3d 100644 --- a/src/components/Reactions/MiniQuickEmojiReactions.js +++ b/src/components/Reactions/MiniQuickEmojiReactions.tsx @@ -1,49 +1,38 @@ -import PropTypes from 'prop-types'; import React, {useRef} from 'react'; import {View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; +import type {Emoji} from '@assets/emojis/types'; import BaseMiniContextMenuItem from '@components/BaseMiniContextMenuItem'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import Text from '@components/Text'; -import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useLocalize from '@hooks/useLocalize'; import useStyleUtils from '@hooks/useStyleUtils'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as EmojiUtils from '@libs/EmojiUtils'; import getButtonState from '@libs/getButtonState'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; import * as Session from '@userActions/Session'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; -import {baseQuickEmojiReactionsDefaultProps, baseQuickEmojiReactionsPropTypes} from './QuickEmojiReactions/BaseQuickEmojiReactions'; +import type {ReportActionReactions} from '@src/types/onyx'; +import type {BaseQuickEmojiReactionsProps} from './QuickEmojiReactions/types'; -const propTypes = { - ...baseQuickEmojiReactionsPropTypes, +type MiniQuickEmojiReactionsOnyxProps = { + /** All the emoji reactions for the report action. */ + emojiReactions: OnyxEntry; + /** The user's preferred skin tone. */ + preferredSkinTone: OnyxEntry; +}; + +type MiniQuickEmojiReactionsProps = BaseQuickEmojiReactionsProps & { /** * Will be called when the user closed the emoji picker * without selecting an emoji. */ - onEmojiPickerClosed: PropTypes.func, - - /** - * ReportAction for EmojiPicker. - */ - reportAction: PropTypes.shape({ - reportActionID: PropTypes.string.isRequired, - }), - - ...withLocalizePropTypes, - preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), -}; - -const defaultProps = { - ...baseQuickEmojiReactionsDefaultProps, - onEmojiPickerClosed: () => {}, - preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, - reportAction: {}, + onEmojiPickerClosed?: () => void; }; /** @@ -51,56 +40,63 @@ const defaultProps = { * emoji picker icon button. This is used for the mini * context menu which we just show on web, when hovering * a message. - * @param {Props} props - * @returns {JSX.Element} */ -function MiniQuickEmojiReactions(props) { +function MiniQuickEmojiReactions({ + reportAction, + onEmojiSelected, + preferredLocale = CONST.LOCALES.DEFAULT, + preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, + emojiReactions = {}, + onPressOpenPicker = () => {}, + onEmojiPickerClosed = () => {}, +}: MiniQuickEmojiReactionsProps) { const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); - const ref = useRef(); + const ref = useRef(null); + const {translate} = useLocalize(); const openEmojiPicker = () => { - props.onPressOpenPicker(); + onPressOpenPicker(); EmojiPickerAction.showEmojiPicker( - props.onEmojiPickerClosed, + onEmojiPickerClosed, (emojiCode, emojiObject) => { - props.onEmojiSelected(emojiObject, props.emojiReactions); + onEmojiSelected(emojiObject, emojiReactions); }, ref, undefined, () => {}, - props.reportAction.reportActionID, + reportAction.reportActionID, ); }; return ( - {_.map(CONST.QUICK_REACTIONS, (emoji) => ( + {CONST.QUICK_REACTIONS.map((emoji: Emoji) => ( props.onEmojiSelected(emoji, props.emojiReactions))} + tooltipText={`:${EmojiUtils.getLocalizedEmojiName(emoji.name, preferredLocale)}:`} + onPress={Session.checkIfActionIsAllowed(() => onEmojiSelected(emoji, emojiReactions))} > - {EmojiUtils.getPreferredEmojiCode(emoji, props.preferredSkinTone)} + {EmojiUtils.getPreferredEmojiCode(emoji, preferredSkinTone)} ))} { - if (!EmojiPickerAction.emojiPickerRef.current.isEmojiPickerVisible) { + if (!EmojiPickerAction.emojiPickerRef.current?.isEmojiPickerVisible) { openEmojiPicker(); } else { - EmojiPickerAction.emojiPickerRef.current.hideEmojiPicker(); + EmojiPickerAction.emojiPickerRef.current?.hideEmojiPicker(); } })} isDelayButtonStateComplete={false} - tooltipText={props.translate('emojiReactions.addReactionTooltip')} + tooltipText={translate('emojiReactions.addReactionTooltip')} > {({hovered, pressed}) => ( `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, - }, - }), -)(MiniQuickEmojiReactions); + +export default withOnyx({ + preferredSkinTone: { + key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, + }, + emojiReactions: { + key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, + }, +})(MiniQuickEmojiReactions); diff --git a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js deleted file mode 100644 index c932632f7bff..000000000000 --- a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.js +++ /dev/null @@ -1,106 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {View} from 'react-native'; -import {withOnyx} from 'react-native-onyx'; -import _ from 'underscore'; -import AddReactionBubble from '@components/Reactions/AddReactionBubble'; -import EmojiReactionBubble from '@components/Reactions/EmojiReactionBubble'; -import EmojiReactionsPropTypes from '@components/Reactions/EmojiReactionsPropTypes'; -import Tooltip from '@components/Tooltip'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as EmojiUtils from '@libs/EmojiUtils'; -import * as Session from '@userActions/Session'; -import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; - -const baseQuickEmojiReactionsPropTypes = { - emojiReactions: EmojiReactionsPropTypes, - - /** - * Callback to fire when an emoji is selected. - */ - onEmojiSelected: PropTypes.func.isRequired, - - /** - * Will be called when the emoji picker is about to show. - */ - onWillShowPicker: PropTypes.func, - - /** - * Callback to fire when the "open emoji picker" button is pressed. - * The function receives an argument which can be called - * to actually open the emoji picker. - */ - onPressOpenPicker: PropTypes.func, - - /** - * ReportAction for EmojiPicker. - */ - reportAction: PropTypes.object, - - preferredLocale: PropTypes.string, -}; - -const baseQuickEmojiReactionsDefaultProps = { - emojiReactions: {}, - onWillShowPicker: () => {}, - onPressOpenPicker: () => {}, - reportAction: {}, - preferredLocale: CONST.LOCALES.DEFAULT, -}; - -const propTypes = { - ...baseQuickEmojiReactionsPropTypes, - preferredSkinTone: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), -}; - -const defaultProps = { - ...baseQuickEmojiReactionsDefaultProps, - preferredSkinTone: CONST.EMOJI_DEFAULT_SKIN_TONE, -}; - -function BaseQuickEmojiReactions(props) { - const styles = useThemeStyles(); - return ( - - {_.map(CONST.QUICK_REACTIONS, (emoji) => ( - - - props.onEmojiSelected(emoji, props.emojiReactions))} - /> - - - ))} - props.onEmojiSelected(emoji, props.emojiReactions)} - reportAction={props.reportAction} - /> - - ); -} - -BaseQuickEmojiReactions.displayName = 'BaseQuickEmojiReactions'; -BaseQuickEmojiReactions.propTypes = propTypes; -BaseQuickEmojiReactions.defaultProps = defaultProps; -export default withOnyx({ - preferredSkinTone: { - key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, - }, - emojiReactions: { - key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, - }, - preferredLocale: { - key: ONYXKEYS.NVP_PREFERRED_LOCALE, - }, -})(BaseQuickEmojiReactions); - -export {baseQuickEmojiReactionsPropTypes, baseQuickEmojiReactionsDefaultProps}; diff --git a/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx new file mode 100644 index 000000000000..58973e90b9c4 --- /dev/null +++ b/src/components/Reactions/QuickEmojiReactions/BaseQuickEmojiReactions.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import {View} from 'react-native'; +import {withOnyx} from 'react-native-onyx'; +import type {Emoji} from '@assets/emojis/types'; +import AddReactionBubble from '@components/Reactions/AddReactionBubble'; +import EmojiReactionBubble from '@components/Reactions/EmojiReactionBubble'; +import Tooltip from '@components/Tooltip'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as EmojiUtils from '@libs/EmojiUtils'; +import * as Session from '@userActions/Session'; +import CONST from '@src/CONST'; +import ONYXKEYS from '@src/ONYXKEYS'; +import type {BaseQuickEmojiReactionsOnyxProps, BaseQuickEmojiReactionsProps} from './types'; + +function BaseQuickEmojiReactions({ + reportAction, + onEmojiSelected, + preferredLocale = CONST.LOCALES.DEFAULT, + preferredSkinTone = CONST.EMOJI_DEFAULT_SKIN_TONE, + emojiReactions = {}, + onPressOpenPicker = () => {}, + onWillShowPicker = () => {}, +}: BaseQuickEmojiReactionsProps) { + const styles = useThemeStyles(); + + return ( + + {CONST.QUICK_REACTIONS.map((emoji: Emoji) => ( + + + onEmojiSelected(emoji, emojiReactions))} + /> + + + ))} + onEmojiSelected(emoji, emojiReactions)} + reportAction={reportAction} + /> + + ); +} + +BaseQuickEmojiReactions.displayName = 'BaseQuickEmojiReactions'; + +export default withOnyx({ + preferredSkinTone: { + key: ONYXKEYS.PREFERRED_EMOJI_SKIN_TONE, + }, + emojiReactions: { + key: ({reportActionID}) => `${ONYXKEYS.COLLECTION.REPORT_ACTIONS_REACTIONS}${reportActionID}`, + }, + preferredLocale: { + key: ONYXKEYS.NVP_PREFERRED_LOCALE, + }, +})(BaseQuickEmojiReactions); diff --git a/src/components/Reactions/QuickEmojiReactions/index.js b/src/components/Reactions/QuickEmojiReactions/index.js deleted file mode 100644 index 0366071f9c81..000000000000 --- a/src/components/Reactions/QuickEmojiReactions/index.js +++ /dev/null @@ -1,38 +0,0 @@ -import PropTypes from 'prop-types'; -import React from 'react'; -import {contextMenuRef} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; -import CONST from '@src/CONST'; -import BaseQuickEmojiReactions, {baseQuickEmojiReactionsPropTypes} from './BaseQuickEmojiReactions'; - -const propTypes = { - ...baseQuickEmojiReactionsPropTypes, - - /** - * Function that can be called to close the - * context menu in which this component is - * rendered. - */ - closeContextMenu: PropTypes.func.isRequired, -}; - -function QuickEmojiReactions(props) { - const onPressOpenPicker = (openPicker) => { - openPicker(contextMenuRef.current.contentRef, { - horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, - vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, - }); - }; - - return ( - - ); -} - -QuickEmojiReactions.displayName = 'QuickEmojiReactions'; -QuickEmojiReactions.propTypes = propTypes; -export default QuickEmojiReactions; diff --git a/src/components/Reactions/QuickEmojiReactions/index.native.js b/src/components/Reactions/QuickEmojiReactions/index.native.tsx similarity index 56% rename from src/components/Reactions/QuickEmojiReactions/index.native.js rename to src/components/Reactions/QuickEmojiReactions/index.native.tsx index c29bd2665e4a..b0eb88b31b68 100644 --- a/src/components/Reactions/QuickEmojiReactions/index.native.js +++ b/src/components/Reactions/QuickEmojiReactions/index.native.tsx @@ -1,41 +1,30 @@ -import PropTypes from 'prop-types'; import React from 'react'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; -import BaseQuickEmojiReactions, {baseQuickEmojiReactionsPropTypes} from './BaseQuickEmojiReactions'; +import BaseQuickEmojiReactions from './BaseQuickEmojiReactions'; +import type {OpenPickerCallback, QuickEmojiReactionsProps} from './types'; -const propTypes = { - ...baseQuickEmojiReactionsPropTypes, - - /** - * Function that can be called to close the - * context menu in which this component is - * rendered. - */ - closeContextMenu: PropTypes.func.isRequired, -}; - -function QuickEmojiReactions(props) { - const onPressOpenPicker = (openPicker) => { +function QuickEmojiReactions({closeContextMenu, ...rest}: QuickEmojiReactionsProps) { + const onPressOpenPicker = (openPicker?: OpenPickerCallback) => { // We first need to close the menu as it's a popover. // The picker is a popover as well and on mobile there can only // be one active popover at a time. - props.closeContextMenu(() => { + closeContextMenu(() => { // As the menu which includes the button to open the emoji picker // gets closed, before the picker actually opens, we pass the composer // ref as anchor for the emoji picker popover. - openPicker(ReportActionComposeFocusManager.composerRef); + openPicker?.(ReportActionComposeFocusManager.composerRef); }); }; return ( ); } QuickEmojiReactions.displayName = 'QuickEmojiReactions'; -QuickEmojiReactions.propTypes = propTypes; + export default QuickEmojiReactions; diff --git a/src/components/Reactions/QuickEmojiReactions/index.tsx b/src/components/Reactions/QuickEmojiReactions/index.tsx new file mode 100644 index 000000000000..3b44f4fe4826 --- /dev/null +++ b/src/components/Reactions/QuickEmojiReactions/index.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import {contextMenuRef} from '@pages/home/report/ContextMenu/ReportActionContextMenu'; +import CONST from '@src/CONST'; +import BaseQuickEmojiReactions from './BaseQuickEmojiReactions'; +import type {OpenPickerCallback, QuickEmojiReactionsProps} from './types'; + +function QuickEmojiReactions({closeContextMenu, ...rest}: QuickEmojiReactionsProps) { + const onPressOpenPicker = (openPicker?: OpenPickerCallback) => { + openPicker?.(contextMenuRef.current?.contentRef, { + horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, + vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.TOP, + }); + }; + + return ( + + ); +} + +QuickEmojiReactions.displayName = 'QuickEmojiReactions'; + +export default QuickEmojiReactions; diff --git a/src/components/Reactions/QuickEmojiReactions/types.ts b/src/components/Reactions/QuickEmojiReactions/types.ts new file mode 100644 index 000000000000..d782d5ae35c7 --- /dev/null +++ b/src/components/Reactions/QuickEmojiReactions/types.ts @@ -0,0 +1,56 @@ +import type {RefObject} from 'react'; +import type {TextInput, View} from 'react-native'; +import type {OnyxEntry} from 'react-native-onyx'; +import type {Emoji} from '@assets/emojis/types'; +import type {AnchorOrigin} from '@userActions/EmojiPickerAction'; +import type {Locale, ReportAction, ReportActionReactions} from '@src/types/onyx'; + +type PickerRefElement = RefObject; + +type OpenPickerCallback = (element?: PickerRefElement, anchorOrigin?: AnchorOrigin) => void; + +type CloseContextMenuCallback = () => void; + +type BaseQuickEmojiReactionsOnyxProps = { + /** All the emoji reactions for the report action. */ + emojiReactions: OnyxEntry; + + /** The user's preferred locale. */ + preferredLocale: OnyxEntry; + + /** The user's preferred skin tone. */ + preferredSkinTone: OnyxEntry; +}; + +type BaseQuickEmojiReactionsProps = BaseQuickEmojiReactionsOnyxProps & { + /** Callback to fire when an emoji is selected. */ + onEmojiSelected: (emoji: Emoji, emojiReactions: OnyxEntry) => void; + + /** + * Will be called when the emoji picker is about to show. + */ + onWillShowPicker?: (callback: CloseContextMenuCallback) => void; + + /** + * Callback to fire when the "open emoji picker" button is pressed. + * The function receives an argument which can be called + * to actually open the emoji picker. + */ + onPressOpenPicker?: (openPicker?: OpenPickerCallback) => void; + + /** ReportAction for EmojiPicker. */ + reportAction: ReportAction; + + /** Id of the ReportAction for EmojiPicker. */ + reportActionID: string; +}; + +type QuickEmojiReactionsProps = BaseQuickEmojiReactionsProps & { + /** + * Function that can be called to close the context menu + * in which this component is rendered. + */ + closeContextMenu: (callback: CloseContextMenuCallback) => void; +}; + +export type {BaseQuickEmojiReactionsProps, BaseQuickEmojiReactionsOnyxProps, QuickEmojiReactionsProps, OpenPickerCallback, CloseContextMenuCallback, PickerRefElement}; diff --git a/src/components/Reactions/ReactionTooltipContent.js b/src/components/Reactions/ReactionTooltipContent.js deleted file mode 100644 index bb6b03c5918b..000000000000 --- a/src/components/Reactions/ReactionTooltipContent.js +++ /dev/null @@ -1,67 +0,0 @@ -import PropTypes from 'prop-types'; -import React, {useMemo} from 'react'; -import {View} from 'react-native'; -import _ from 'underscore'; -import Text from '@components/Text'; -import {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; -import withLocalize from '@components/withLocalize'; -import useThemeStyles from '@hooks/useThemeStyles'; -import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; - -const propTypes = { - /** - * A list of emoji codes to display in the tooltip. - */ - emojiCodes: PropTypes.arrayOf(PropTypes.string).isRequired, - - /** - * The name of the emoji to display in the tooltip. - */ - emojiName: PropTypes.string.isRequired, - - /** - * A list of account IDs to display in the tooltip. - */ - accountIDs: PropTypes.arrayOf(PropTypes.number).isRequired, - - ...withCurrentUserPersonalDetailsPropTypes, -}; - -const defaultProps = { - ...withCurrentUserPersonalDetailsDefaultProps, -}; - -function ReactionTooltipContent(props) { - const styles = useThemeStyles(); - const users = useMemo( - () => PersonalDetailsUtils.getPersonalDetailsByIDs(props.accountIDs, props.currentUserPersonalDetails.accountID, true), - [props.currentUserPersonalDetails.accountID, props.accountIDs], - ); - const namesString = _.filter( - _.map(users, (user) => user && user.displayName), - (n) => n, - ).join(', '); - return ( - - - {_.map(props.emojiCodes, (emojiCode) => ( - - {emojiCode} - - ))} - - - {namesString} - - {`${props.translate('emojiReactions.reactedWith')} :${props.emojiName}:`} - - ); -} - -ReactionTooltipContent.propTypes = propTypes; -ReactionTooltipContent.defaultProps = defaultProps; -ReactionTooltipContent.displayName = 'ReactionTooltipContent'; -export default React.memo(withLocalize(ReactionTooltipContent)); diff --git a/src/components/Reactions/ReactionTooltipContent.tsx b/src/components/Reactions/ReactionTooltipContent.tsx new file mode 100644 index 000000000000..198eba1f969c --- /dev/null +++ b/src/components/Reactions/ReactionTooltipContent.tsx @@ -0,0 +1,58 @@ +import React, {useMemo} from 'react'; +import {View} from 'react-native'; +import Text from '@components/Text'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; +import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; + +type ReactionTooltipContentProps = Pick & { + /** + * A list of emoji codes to display in the tooltip. + */ + emojiCodes: string[]; + + /** + * The name of the emoji to display in the tooltip. + */ + emojiName: string; + + /** + * A list of account IDs to display in the tooltip. + */ + accountIDs: number[]; +}; + +function ReactionTooltipContent({accountIDs, currentUserPersonalDetails = {}, emojiCodes, emojiName}: ReactionTooltipContentProps) { + const styles = useThemeStyles(); + const {translate} = useLocalize(); + const users = useMemo(() => PersonalDetailsUtils.getPersonalDetailsByIDs(accountIDs, currentUserPersonalDetails.accountID, true), [currentUserPersonalDetails.accountID, accountIDs]); + + const namesString = users + .map((user) => user?.displayName) + .filter((name) => name) + .join(', '); + + return ( + + + {emojiCodes.map((emojiCode) => ( + + {emojiCode} + + ))} + + + {namesString} + + {`${translate('emojiReactions.reactedWith')} :${emojiName}:`} + + ); +} + +ReactionTooltipContent.displayName = 'ReactionTooltipContent'; + +export default React.memo(ReactionTooltipContent); diff --git a/src/components/Reactions/ReportActionItemEmojiReactions.js b/src/components/Reactions/ReportActionItemEmojiReactions.tsx similarity index 52% rename from src/components/Reactions/ReportActionItemEmojiReactions.js rename to src/components/Reactions/ReportActionItemEmojiReactions.tsx index 547f4089857f..d1a2cf56b6a5 100644 --- a/src/components/Reactions/ReportActionItemEmojiReactions.js +++ b/src/components/Reactions/ReportActionItemEmojiReactions.tsx @@ -1,63 +1,98 @@ -import lodashGet from 'lodash/get'; -import PropTypes from 'prop-types'; +import sortBy from 'lodash/sortBy'; import React, {useContext, useRef} from 'react'; import {View} from 'react-native'; -import _ from 'underscore'; +import type {OnyxEntry} from 'react-native-onyx'; +import type {Emoji} from '@assets/emojis/types'; import OfflineWithFeedback from '@components/OfflineWithFeedback'; import Tooltip from '@components/Tooltip'; -import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsDefaultProps, withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; -import withLocalize from '@components/withLocalize'; +import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import useThemeStyles from '@hooks/useThemeStyles'; -import compose from '@libs/compose'; import * as EmojiUtils from '@libs/EmojiUtils'; -import reportActionPropTypes from '@pages/home/report/reportActionPropTypes'; import {ReactionListContext} from '@pages/home/ReportScreenContext'; +import type {ReactionListAnchor, ReactionListEvent} from '@pages/home/ReportScreenContext'; +import CONST from '@src/CONST'; +import type {Locale, ReportAction, ReportActionReactions} from '@src/types/onyx'; +import type {PendingAction} from '@src/types/onyx/OnyxCommon'; import AddReactionBubble from './AddReactionBubble'; import EmojiReactionBubble from './EmojiReactionBubble'; -import EmojiReactionsPropTypes from './EmojiReactionsPropTypes'; import ReactionTooltipContent from './ReactionTooltipContent'; -const propTypes = { - emojiReactions: EmojiReactionsPropTypes, +type ReportActionItemEmojiReactionsProps = WithCurrentUserPersonalDetailsProps & { + /** All the emoji reactions for the report action. */ + emojiReactions: OnyxEntry; + + /** The user's preferred locale. */ + preferredLocale: OnyxEntry; /** The report action that these reactions are for */ - reportAction: PropTypes.shape(reportActionPropTypes).isRequired, + reportAction: ReportAction; /** * Function to call when the user presses on an emoji. * This can also be an emoji the user already reacted with, * hence this function asks to toggle the reaction by emoji. */ - toggleReaction: PropTypes.func.isRequired, + toggleReaction: (emoji: Emoji) => void; /** We disable reacting with emojis on report actions that have errors */ - shouldBlockReactions: PropTypes.bool, - - ...withCurrentUserPersonalDetailsPropTypes, + shouldBlockReactions?: boolean; }; -const defaultProps = { - ...withCurrentUserPersonalDetailsDefaultProps, - emojiReactions: {}, - shouldBlockReactions: false, +type PopoverReactionListAnchors = Record; + +type FormattedReaction = { + /** The emoji codes to display in the bubble */ + emojiCodes: string[]; + + /** IDs of users used the reaction */ + userAccountIDs: number[]; + + /** Total reaction count */ + reactionCount: number; + + /** Whether the current account has reacted to the report action */ + hasUserReacted: boolean; + + /** Oldest timestamp of when the emoji was added */ + oldestTimestamp: string; + + /** Callback to fire on press */ + onPress: () => void; + + /** Callback to fire on reaction list open */ + onReactionListOpen: (event: ReactionListEvent) => void; + + /** The name of the emoji */ + reactionEmojiName: string; + + /** The type of action that's pending */ + pendingAction: PendingAction; }; -function ReportActionItemEmojiReactions(props) { +function ReportActionItemEmojiReactions({ + reportAction, + currentUserPersonalDetails, + toggleReaction, + emojiReactions = {}, + shouldBlockReactions = false, + preferredLocale = CONST.LOCALES.DEFAULT, +}: ReportActionItemEmojiReactionsProps) { const styles = useThemeStyles(); const reactionListRef = useContext(ReactionListContext); - const popoverReactionListAnchors = useRef({}); + const popoverReactionListAnchors = useRef({}); let totalReactionCount = 0; - const reportAction = props.reportAction; const reportActionID = reportAction.reportActionID; - const formattedReactions = _.chain(props.emojiReactions) - .map((emojiReaction, emojiName) => { + // Each emoji is sorted by the oldest timestamp of user reactions so that they will always appear in the same order for everyone + const formattedReactions: Array = sortBy( + Object.entries(emojiReactions ?? {}).map(([emojiName, emojiReaction]) => { const {emoji, emojiCodes, reactionCount, hasUserReacted, userAccountIDs, oldestTimestamp} = EmojiUtils.getEmojiReactionDetails( emojiName, emojiReaction, - props.currentUserPersonalDetails.accountID, + currentUserPersonalDetails.accountID, ); if (reactionCount === 0) { @@ -66,11 +101,11 @@ function ReportActionItemEmojiReactions(props) { totalReactionCount += reactionCount; const onPress = () => { - props.toggleReaction(emoji); + toggleReaction(emoji); }; - const onReactionListOpen = (event) => { - reactionListRef.current.showReactionList(event, popoverReactionListAnchors.current[emojiName], emojiName, reportActionID); + const onReactionListOpen = (event: ReactionListEvent) => { + reactionListRef?.current?.showReactionList(event, popoverReactionListAnchors.current[emojiName], emojiName, reportActionID); }; return { @@ -84,15 +119,14 @@ function ReportActionItemEmojiReactions(props) { reactionEmojiName: emojiName, pendingAction: emojiReaction.pendingAction, }; - }) - // Each emoji is sorted by the oldest timestamp of user reactions so that they will always appear in the same order for everyone - .sortBy('oldestTimestamp') - .value(); + }), + ['oldestTimestamp'], + ); return ( totalReactionCount > 0 && ( - {_.map(formattedReactions, (reaction) => { + {formattedReactions.map((reaction) => { if (reaction === null) { return; } @@ -100,19 +134,19 @@ function ReportActionItemEmojiReactions(props) { ( )} - renderTooltipContentKey={[..._.map(reaction.userAccountIDs, String), ...reaction.emojiCodes]} + renderTooltipContentKey={[...reaction.userAccountIDs.map(String), ...reaction.emojiCodes]} key={reaction.reactionEmojiName} > (popoverReactionListAnchors.current[reaction.reactionEmojiName] = ref)} @@ -121,17 +155,17 @@ function ReportActionItemEmojiReactions(props) { onPress={reaction.onPress} hasUserReacted={reaction.hasUserReacted} onReactionListOpen={reaction.onReactionListOpen} - shouldBlockReactions={props.shouldBlockReactions} + shouldBlockReactions={shouldBlockReactions} /> ); })} - {!props.shouldBlockReactions && ( + {!shouldBlockReactions && ( )} @@ -140,6 +174,5 @@ function ReportActionItemEmojiReactions(props) { } ReportActionItemEmojiReactions.displayName = 'ReportActionItemReactions'; -ReportActionItemEmojiReactions.propTypes = propTypes; -ReportActionItemEmojiReactions.defaultProps = defaultProps; -export default compose(withLocalize, withCurrentUserPersonalDetails)(ReportActionItemEmojiReactions); + +export default withCurrentUserPersonalDetails(ReportActionItemEmojiReactions); diff --git a/src/components/ReportActionItem/MoneyReportView.tsx b/src/components/ReportActionItem/MoneyReportView.tsx index 36daa037fd78..16ea27b17f42 100644 --- a/src/components/ReportActionItem/MoneyReportView.tsx +++ b/src/components/ReportActionItem/MoneyReportView.tsx @@ -1,5 +1,6 @@ import React from 'react'; -import {StyleProp, TextStyle, View} from 'react-native'; +import type {StyleProp, TextStyle} from 'react-native'; +import {View} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import SpacerView from '@components/SpacerView'; diff --git a/src/components/ReportActionItem/MoneyRequestPreview.js b/src/components/ReportActionItem/MoneyRequestPreview.js index f0e818ddff4d..d425e236431b 100644 --- a/src/components/ReportActionItem/MoneyRequestPreview.js +++ b/src/components/ReportActionItem/MoneyRequestPreview.js @@ -1,3 +1,4 @@ +import {truncate} from 'lodash'; import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; import React from 'react'; @@ -151,8 +152,9 @@ function MoneyRequestPreview(props) { // Pay button should only be visible to the manager of the report. const isCurrentUserManager = managerID === sessionAccountID; - const {amount: requestAmount, currency: requestCurrency, comment: requestComment, merchant: requestMerchant} = ReportUtils.getTransactionDetails(props.transaction); - const description = requestComment; + const {amount: requestAmount, currency: requestCurrency, comment: requestComment, merchant} = ReportUtils.getTransactionDetails(props.transaction); + const description = truncate(requestComment, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); + const requestMerchant = truncate(merchant, {length: CONST.REQUEST_PREVIEW.MAX_LENGTH}); const hasReceipt = TransactionUtils.hasReceipt(props.transaction); const isScanning = hasReceipt && TransactionUtils.isReceiptBeingScanned(props.transaction); const hasFieldErrors = TransactionUtils.hasMissingSmartscanFields(props.transaction); diff --git a/src/components/ReportActionItem/MoneyRequestView.js b/src/components/ReportActionItem/MoneyRequestView.js index 514dc71ffe2c..3437058efa45 100644 --- a/src/components/ReportActionItem/MoneyRequestView.js +++ b/src/components/ReportActionItem/MoneyRequestView.js @@ -1,7 +1,7 @@ import lodashGet from 'lodash/get'; import lodashValues from 'lodash/values'; import PropTypes from 'prop-types'; -import React, {useMemo} from 'react'; +import React, {useCallback, useMemo} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import categoryPropTypes from '@components/categoryPropTypes'; @@ -14,12 +14,14 @@ import Switch from '@components/Switch'; import tagPropTypes from '@components/tagPropTypes'; import Text from '@components/Text'; import transactionPropTypes from '@components/transactionPropTypes'; +import ViolationMessages from '@components/ViolationMessages'; import withCurrentUserPersonalDetails, {withCurrentUserPersonalDetailsPropTypes} from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import usePermissions from '@hooks/usePermissions'; import useStyleUtils from '@hooks/useStyleUtils'; import useTheme from '@hooks/useTheme'; import useThemeStyles from '@hooks/useThemeStyles'; +import useViolations from '@hooks/useViolations'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as CardUtils from '@libs/CardUtils'; import compose from '@libs/compose'; @@ -41,6 +43,32 @@ import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; import ReportActionItemImage from './ReportActionItemImage'; +const violationNames = lodashValues(CONST.VIOLATIONS); + +const transactionViolationPropType = PropTypes.shape({ + type: PropTypes.string.isRequired, + name: PropTypes.oneOf(violationNames).isRequired, + data: PropTypes.shape({ + rejectedBy: PropTypes.string, + rejectReason: PropTypes.string, + amount: PropTypes.string, + surcharge: PropTypes.number, + invoiceMarkup: PropTypes.number, + maxAge: PropTypes.number, + tagName: PropTypes.string, + formattedLimitAmount: PropTypes.string, + categoryLimit: PropTypes.string, + limit: PropTypes.string, + category: PropTypes.string, + brokenBankConnection: PropTypes.bool, + isAdmin: PropTypes.bool, + email: PropTypes.string, + isTransactionOlderThan7Days: PropTypes.bool, + member: PropTypes.string, + taxName: PropTypes.string, + }), +}); + const propTypes = { /** The report currently being looked at */ report: reportPropTypes.isRequired, @@ -61,6 +89,9 @@ const propTypes = { /** The transaction associated with the transactionThread */ transaction: transactionPropTypes, + /** Violations detected in this transaction */ + transactionViolations: PropTypes.arrayOf(transactionViolationPropType), + /** Collection of tags attached to a policy */ policyTags: tagPropTypes, @@ -76,10 +107,11 @@ const defaultProps = { currency: CONST.CURRENCY.USD, comment: {comment: ''}, }, + transactionViolations: [], policyTags: {}, }; -function MoneyRequestView({report, parentReport, parentReportActions, policyCategories, shouldShowHorizontalRule, transaction, policyTags, policy}) { +function MoneyRequestView({report, parentReport, parentReportActions, policyCategories, shouldShowHorizontalRule, transaction, policyTags, policy, transactionViolations}) { const theme = useTheme(); const styles = useThemeStyles(); const StyleUtils = useStyleUtils(); @@ -116,7 +148,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const isSettled = ReportUtils.isSettled(moneyRequestReport.reportID); const isCancelled = moneyRequestReport && moneyRequestReport.isCancelledIOU; const canEdit = ReportUtils.canEditMoneyRequest(parentReportAction); - const canEditAmount = canEdit && !isSettled && !isCardTransaction; + const canEditAmount = ReportUtils.canEditMoneyRequest(parentReportAction, CONST.EDIT_REQUEST_FIELD.AMOUNT, transaction) && !isSettled && !isCardTransaction; const canEditReceipt = ReportUtils.canEditFieldOfMoneyRequest(parentReportAction, moneyRequestReport.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT); // A flag for verifying that the current report is a sub-report of a workspace chat @@ -131,6 +163,9 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate const shouldShowTag = isPolicyExpenseChat && (transactionTag || OptionsListUtils.hasEnabledOptions(lodashValues(policyTagsList))); const shouldShowBillable = isPolicyExpenseChat && (transactionBillable || !lodashGet(policy, 'disabledFields.defaultBillable', true)); + const {getViolationsForField} = useViolations(transactionViolations); + const hasViolations = useCallback((field) => canUseViolations && getViolationsForField(field).length > 0, [canUseViolations, getViolationsForField]); + let amountDescription = `${translate('iou.amount')}`; if (isCardTransaction) { @@ -198,6 +233,7 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.RECEIPT))} /> )} + {canUseViolations && } Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.AMOUNT))} - brickRoadIndicator={hasErrors && transactionAmount === 0 ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={hasViolations('amount') || (hasErrors && transactionAmount === 0) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} error={hasErrors && transactionAmount === 0 ? translate('common.error.enterAmount') : ''} /> + {canUseViolations && } Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DESCRIPTION))} wrapperStyle={[styles.pv2, styles.taskDescriptionMenuItem]} + brickRoadIndicator={hasViolations('comment') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} numberOfLinesTitle={0} /> + {canUseViolations && } {isDistanceRequest ? ( @@ -245,9 +284,10 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate shouldShowRightIcon={canEdit} titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.MERCHANT))} - brickRoadIndicator={hasErrors && isEmptyMerchant ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={hasViolations('merchant') || (hasErrors && isEmptyMerchant) ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} error={hasErrors && isEmptyMerchant ? translate('common.error.enterMerchant') : ''} /> + {canUseViolations && } )} @@ -258,9 +298,10 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate shouldShowRightIcon={canEdit && !isSettled} titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.DATE))} - brickRoadIndicator={hasErrors && transactionDate === '' ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} + brickRoadIndicator={hasViolations('date') || (hasErrors && transactionDate === '') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} error={hasErrors && transactionDate === '' ? translate('common.error.enterDate') : ''} /> + {canUseViolations && } {shouldShowCategory && ( @@ -271,7 +312,9 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate shouldShowRightIcon={canEdit} titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.CATEGORY))} + brickRoadIndicator={hasViolations('category') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} /> + {canUseViolations && } )} {shouldShowTag && ( @@ -283,7 +326,9 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate shouldShowRightIcon={canEdit} titleStyle={styles.flex1} onPress={() => Navigation.navigate(ROUTES.EDIT_REQUEST.getRoute(report.reportID, CONST.EDIT_REQUEST_FIELD.TAG))} + brickRoadIndicator={hasViolations('tag') ? CONST.BRICK_ROAD_INDICATOR_STATUS.ERROR : ''} /> + {canUseViolations && } )} {isCardTransaction && ( @@ -295,15 +340,24 @@ function MoneyRequestView({report, parentReport, parentReportActions, policyCate /> )} + {shouldShowBillable && ( - - {translate('common.billable')} - IOU.editMoneyRequest(transaction, report.reportID, {billable: value})} - /> - + <> + + {translate('common.billable')} + IOU.editMoneyRequest(transaction, report.reportID, {billable: value})} + /> + + {hasViolations('billable') && ( + + )} + )} { + const parentReportAction = ReportActionsUtils.getParentReportAction(report); + const transactionID = lodashGet(parentReportAction, ['originalMessage', 'IOUTransactionID'], 0); + return `${ONYXKEYS.COLLECTION.TRANSACTION_VIOLATIONS}${transactionID}`; + }, + }, + policyTags: { + key: ({report}) => `${ONYXKEYS.COLLECTION.POLICY_TAGS}${report.policyID}`, + }, }), )(MoneyRequestView); diff --git a/src/components/ReportActionItem/RenameAction.tsx b/src/components/ReportActionItem/RenameAction.tsx index ef9317ecac0e..b4b2553d652a 100644 --- a/src/components/ReportActionItem/RenameAction.tsx +++ b/src/components/ReportActionItem/RenameAction.tsx @@ -1,6 +1,7 @@ import React from 'react'; import Text from '@components/Text'; -import withCurrentUserPersonalDetails, {type WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; +import withCurrentUserPersonalDetails from '@components/withCurrentUserPersonalDetails'; +import type {WithCurrentUserPersonalDetailsProps} from '@components/withCurrentUserPersonalDetails'; import useLocalize from '@hooks/useLocalize'; import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; diff --git a/src/components/ReportActionItem/ReportActionItemImage.js b/src/components/ReportActionItem/ReportActionItemImage.js index 2c5ef22b1b8e..4336a5eddd8a 100644 --- a/src/components/ReportActionItem/ReportActionItemImage.js +++ b/src/components/ReportActionItem/ReportActionItemImage.js @@ -1,3 +1,4 @@ +import Str from 'expensify-common/lib/str'; import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; @@ -60,7 +61,7 @@ function ReportActionItemImage({thumbnail, image, enablePreviewModal, transactio ); - } else if (thumbnail && !isLocalFile) { + } else if (thumbnail && !isLocalFile && !Str.isPDF(imageSource)) { receiptImageComponent = ( - { - Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(props.iouReportID)); - }} - onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} - onPressOut={() => ControlSelection.unblock()} - onLongPress={(event) => showContextMenuForReport(event, props.contextMenuAnchor, props.chatReportID, props.action, props.checkIfContextMenuActive)} - style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox]} - role="button" - accessibilityLabel={props.translate('iou.viewDetails')} - > - - {hasReceipts && ( - - )} - - - - {getPreviewMessage()} - - {!iouSettled && hasErrors && ( - - )} - - - - {getDisplayAmount()} - {ReportUtils.isSettled(props.iouReportID) && ( - - - + + + { + Navigation.navigate(ROUTES.REPORT_WITH_ID.getRoute(props.iouReportID)); + }} + onPressIn={() => DeviceCapabilities.canUseTouchScreen() && ControlSelection.block()} + onPressOut={() => ControlSelection.unblock()} + onLongPress={(event) => showContextMenuForReport(event, props.contextMenuAnchor, props.chatReportID, props.action, props.checkIfContextMenuActive)} + style={[styles.flexRow, styles.justifyContentBetween, styles.reportPreviewBox]} + role="button" + accessibilityLabel={props.translate('iou.viewDetails')} + > + + {hasReceipts && ( + + )} + + + + {getPreviewMessage()} + + {!iouSettled && hasErrors && ( + )} - - {!isScanning && (numberOfRequests > 1 || hasReceipts) && ( - {previewSubtitle || moneyRequestComment} + {getDisplayAmount()} + {ReportUtils.isSettled(props.iouReportID) && ( + + + + )} - )} - {shouldShowSettlementButton && ( - IOU.payMoneyRequest(paymentType, props.chatReport, props.iouReport)} - enablePaymentsRoute={ROUTES.ENABLE_PAYMENTS} - addBankAccountRoute={bankAccountRoute} - shouldHidePaymentOptions={!shouldShowPayButton} - shouldShowApproveButton={shouldShowApproveButton} - style={[styles.mt3]} - kycWallAnchorAlignment={{ - horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.LEFT, - vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, - }} - paymentMethodDropdownAnchorAlignment={{ - horizontal: CONST.MODAL.ANCHOR_ORIGIN_HORIZONTAL.RIGHT, - vertical: CONST.MODAL.ANCHOR_ORIGIN_VERTICAL.BOTTOM, - }} - /> - )} - {shouldShowSubmitButton && ( -