diff --git a/.eslintrc.js b/.eslintrc.js index b71338d0c1a5..85a4e86797b6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -174,6 +174,7 @@ module.exports = { 'rulesdir/prefer-underscore-method': 'off', 'rulesdir/prefer-import-module-contents': 'off', 'react/require-default-props': 'off', + 'react/prop-types': 'off', 'no-restricted-syntax': [ 'error', { diff --git a/.github/scripts/findUnusedKeys.sh b/.github/scripts/findUnusedKeys.sh index 1411fffc8389..193149e609af 100755 --- a/.github/scripts/findUnusedKeys.sh +++ b/.github/scripts/findUnusedKeys.sh @@ -6,10 +6,11 @@ LIB_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && cd ../../ && pwd)" readonly SRC_DIR="${LIB_PATH}/src" readonly STYLES_DIR="${LIB_PATH}/src/styles" -readonly STYLES_FILE="${LIB_PATH}/src/styles/styles.ts" -readonly UTILITIES_STYLES_FILE="${LIB_PATH}/src/styles/utilities" +readonly STYLES_FILE="${LIB_PATH}/src/styles/index.ts" +readonly UTILS_STYLES_FILE="${LIB_PATH}/src/styles/utils" +readonly UTILS_STYLES_GENERATORS_FILE="${LIB_PATH}/src/styles/utils/generators" readonly STYLES_KEYS_FILE="${LIB_PATH}/scripts/style_keys_list_temp.txt" -readonly UTILITY_STYLES_KEYS_FILE="${LIB_PATH}/scripts/utility_keys_list_temp.txt" +readonly UTIL_STYLES_KEYS_FILE="${LIB_PATH}/scripts/util_keys_list_temp.txt" readonly REMOVAL_KEYS_FILE="${LIB_PATH}/scripts/removal_keys_list_temp.txt" readonly AMOUNT_LINES_TO_SHOW=3 @@ -29,7 +30,7 @@ ctrl_c() { delete_temp_files exit 1 } - + count_lines() { local file=$1 if [[ -e "$file" ]]; then @@ -43,11 +44,11 @@ count_lines() { show_unused_style_keywords() { while IFS=: read -r key file line_number; do title "File: $file:$line_number" - + # Get lines before and after the error line local lines_before=$((line_number - AMOUNT_LINES_TO_SHOW)) local lines_after=$((line_number + AMOUNT_LINES_TO_SHOW)) - + # Read the lines into an array local lines=() while IFS= read -r line; do @@ -84,14 +85,14 @@ lookfor_unused_keywords() { # Search for keywords starting with "styles" while IFS= read -r keyword; do - + # Remove any [ ] characters from the keyword local clean_keyword="${keyword//[\[\]]/}" # skip styles. keyword that might be used in comments if [[ "$clean_keyword" == "styles." ]]; then continue fi - + if ! remove_keyword "$clean_keyword" ; then # In case of a leaf of the styles object is being used, it means the parent objects is being used # we need to mark it as used. @@ -99,7 +100,7 @@ lookfor_unused_keywords() { # Keyword has more than two words, remove words after the second word local keyword_prefix="${clean_keyword%.*}" remove_keyword "$keyword_prefix" - fi + fi fi done < <(grep -E -o '\bstyles\.[a-zA-Z0-9_.]*' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') done < <(find "${SRC_DIR}" -type f \( "${FILE_EXTENSIONS[@]}" \)) @@ -134,10 +135,10 @@ find_styles_object_and_store_keys() { if [[ ! "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*\} ]]; then continue fi - + if [[ "$line" =~ ^[[:space:]]*(const|let|var)[[:space:]]+([a-zA-Z0-9_-]+)[[:space:]]*=[[:space:]]*\{ ]]; then key="${BASH_REMATCH[2]%%:*{*)}" - echo "styles.${key}|...${key}|${base_name}.${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" + echo "styles.${key}|...${key}|${base_name}.${key}:${file}:${line_number}" >> "$STYLES_KEYS_FILE" fi done < "$file" } @@ -225,7 +226,7 @@ find_theme_style_and_store_keys() { continue fi - + if [[ "$line" =~ ^[[:space:]]*([a-zA-Z0-9_-]+\.)?[a-zA-Z0-9_-]+:[[:space:]]*\{|^[[:space:]]*([a-zA-Z0-9_-])+:[[:space:]]*\(.*\)[[:space:]]*'=>'[[:space:]]*\(\{ ]]; then # Removing all the extra lines after the ":" local key="${line%%:*}" @@ -295,63 +296,64 @@ lookfor_unused_spread_keywords() { done < "$STYLES_FILE" } -find_utility_styles_store_prefix() { +find_util_styles_store_prefix() { # Loop through all files in the src folder while read -r file; do # Search for keywords starting with "styles" while IFS= read -r keyword; do local variable="${keyword##*/}" local variable_trimmed="${variable// /}" # Trim spaces - - echo "$variable_trimmed" >> "$UTILITY_STYLES_KEYS_FILE" - done < <(grep -E -o './utilities/[a-zA-Z0-9_-]+' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') + + echo "$variable_trimmed" >> "$UTIL_STYLES_KEYS_FILE" + done < <(grep -E -o './utils/[a-zA-Z0-9_-]+' "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') done < <(find "${STYLES_DIR}" -type f \( "${FILE_EXTENSIONS[@]}" \)) # Sort and remove duplicates from the temporary file - sort -u -o "${UTILITY_STYLES_KEYS_FILE}" "${UTILITY_STYLES_KEYS_FILE}" + sort -u -o "${UTIL_STYLES_KEYS_FILE}" "${UTIL_STYLES_KEYS_FILE}" } -find_utility_usage_as_styles() { +find_util_usage_as_styles() { while read -r file; do local root_key local parent_dir - # Get the folder name, given this utility files are index.js + # Get the folder name, given this util files are index.js parent_dir=$(dirname "$file") root_key=$(basename "${parent_dir}") - if [[ "${root_key}" == "utilities" ]]; then + if [[ "${root_key}" == "utils" ]]; then continue fi find_theme_style_and_store_keys "${file}" 0 "${root_key}" - done < <(find "${UTILITIES_STYLES_FILE}" -type f \( "${FILE_EXTENSIONS[@]}" \)) + done < <(find "${UTILS_STYLES_FILE}" -type f \( -path "${UTILS_STYLES_GENERATORS_FILE}" -prune -o -name "${FILE_EXTENSIONS[@]}" \) -print) + } -lookfor_unused_utilities() { - # Read each utility keyword from the file +lookfor_unused_utils() { + # Read each util keyword from the file while read -r keyword; do - # Creating a copy so later the replacement can reference it + # Creating a copy so later the replacement can reference it local original_keyword="${keyword}" # Iterate through all files in "src/styles" while read -r file; do # Find all words that match "$keyword.[a-zA-Z0-9_-]+" while IFS= read -r match; do - # Replace the utility prefix with "styles" + # Replace the util prefix with "styles" local variable="${match/#$original_keyword/styles}" # Call the remove_keyword function with the variable remove_keyword "${variable}" remove_keyword "${match}" done < <(grep -E -o "$original_keyword\.[a-zA-Z0-9_-]+" "$file" | grep -v '\/\/' | grep -vE '\/\*.*\*\/') done < <(find "${STYLES_DIR}" -type f \( "${FILE_EXTENSIONS[@]}" \)) - done < "$UTILITY_STYLES_KEYS_FILE" + done < "$UTIL_STYLES_KEYS_FILE" } echo "🔍 Looking for styles." -# Find and store the name of the utility files as keys -find_utility_styles_store_prefix -find_utility_usage_as_styles +# Find and store the name of the util files as keys +find_util_styles_store_prefix +find_util_usage_as_styles # Find and store keys from styles.ts find_styles_object_and_store_keys "$STYLES_FILE" @@ -360,8 +362,8 @@ collect_theme_keys_from_styles "$STYLES_FILE" echo "🗄️ Now going through the codebase and looking for unused keys." -# Look for usages of utilities into src/styles -lookfor_unused_utilities +# Look for usages of utils into src/styles +lookfor_unused_utils lookfor_unused_spread_keywords lookfor_unused_keywords diff --git a/.github/workflows/createNewVersion.yml b/.github/workflows/createNewVersion.yml index 5f7f95e102e3..f772bfb818f0 100644 --- a/.github/workflows/createNewVersion.yml +++ b/.github/workflows/createNewVersion.yml @@ -32,12 +32,6 @@ on: OS_BOTIFY_COMMIT_TOKEN: description: OSBotify personal access token, used to workaround committing to protected branch required: true - OS_BOTIFY_APP_ID: - description: Application ID for OS Botify App - required: true - OS_BOTIFY_PRIVATE_KEY: - description: OSBotify private key - required: true jobs: validateActor: @@ -76,18 +70,16 @@ jobs: token: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }} - name: Setup git for OSBotify - uses: ./.github/actions/composite/setupGitForOSBotifyApp + uses: ./.github/actions/composite/setupGitForOSBotify id: setupGitForOSBotify with: GPG_PASSPHRASE: ${{ secrets.LARGE_SECRET_PASSPHRASE }} - OS_BOTIFY_APP_ID: ${{ secrets.OS_BOTIFY_APP_ID }} - OS_BOTIFY_PRIVATE_KEY: ${{ secrets.OS_BOTIFY_PRIVATE_KEY }} - name: Generate version id: bumpVersion uses: ./.github/actions/javascript/bumpVersion with: - GITHUB_TOKEN: ${{ steps.setupGitForOSBotify.outputs.OS_BOTIFY_API_TOKEN }} + GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }} SEMVER_LEVEL: ${{ inputs.SEMVER_LEVEL }} - name: Commit new version diff --git a/.storybook/theme.js b/.storybook/theme.js index 67898fb00943..08d8b584d580 100644 --- a/.storybook/theme.js +++ b/.storybook/theme.js @@ -1,5 +1,5 @@ import {create} from '@storybook/theming'; -import colors from '../src/styles/colors'; +import colors from '../src/styles/theme/colors'; export default create({ brandTitle: 'New Expensify UI Docs', diff --git a/android/app/build.gradle b/android/app/build.gradle index 9c6be1eff93b..dbc0935e9b76 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -91,8 +91,8 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion multiDexEnabled rootProject.ext.multiDexEnabled - versionCode 1001041123 - versionName "1.4.11-23" + versionCode 1001041300 + versionName "1.4.13-0" } flavorDimensions "default" diff --git a/desktop/package-lock.json b/desktop/package-lock.json index bfeb58ceec05..39b186beb022 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -9,7 +9,7 @@ "dependencies": { "electron-context-menu": "^2.3.0", "electron-log": "^4.4.7", - "electron-serve": "^1.0.0", + "electron-serve": "^1.2.0", "electron-updater": "^6.1.6", "node-machine-id": "^1.1.12" } @@ -145,9 +145,15 @@ "integrity": "sha512-uFZQdgevOp9Fn5lDOrJMU/bmmYxDLZitbIHJM7VXN+cpB59ZnPt1FQL4bOf/Dl2gaIMPYJEfXx38GvJma5iV6A==" }, "node_modules/electron-serve": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/electron-serve/-/electron-serve-1.1.0.tgz", - "integrity": "sha512-tQJBCbXKoKCfkBC143QCqnEtT1s8dNE2V+b/82NF6lxnGO/2Q3a3GSLHtKl3iEDQgdzTf9pH7p418xq2rXbz1Q==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/electron-serve/-/electron-serve-1.2.0.tgz", + "integrity": "sha512-zJG3wisMrDn2G/gnjrhyB074COvly1FnS0U7Edm8bfXLB8MYX7UtwR9/y2LkFreYjzQHm9nEbAfgCmF+9M9LHQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/electron-updater": { "version": "6.1.6", @@ -530,9 +536,9 @@ "integrity": "sha512-uFZQdgevOp9Fn5lDOrJMU/bmmYxDLZitbIHJM7VXN+cpB59ZnPt1FQL4bOf/Dl2gaIMPYJEfXx38GvJma5iV6A==" }, "electron-serve": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/electron-serve/-/electron-serve-1.1.0.tgz", - "integrity": "sha512-tQJBCbXKoKCfkBC143QCqnEtT1s8dNE2V+b/82NF6lxnGO/2Q3a3GSLHtKl3iEDQgdzTf9pH7p418xq2rXbz1Q==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/electron-serve/-/electron-serve-1.2.0.tgz", + "integrity": "sha512-zJG3wisMrDn2G/gnjrhyB074COvly1FnS0U7Edm8bfXLB8MYX7UtwR9/y2LkFreYjzQHm9nEbAfgCmF+9M9LHQ==" }, "electron-updater": { "version": "6.1.6", diff --git a/desktop/package.json b/desktop/package.json index a6b92bde81c4..7689c18f0dbd 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -6,7 +6,7 @@ "dependencies": { "electron-context-menu": "^2.3.0", "electron-log": "^4.4.7", - "electron-serve": "^1.0.0", + "electron-serve": "^1.2.0", "electron-updater": "^6.1.6", "node-machine-id": "^1.1.12" }, diff --git a/docs/_includes/floating-concierge-button.html b/docs/_includes/floating-concierge-button.html deleted file mode 100644 index ed183058388f..000000000000 --- a/docs/_includes/floating-concierge-button.html +++ /dev/null @@ -1,5 +0,0 @@ -{% include CONST.html %} - - - Chat with concierge - diff --git a/docs/_includes/platform.html b/docs/_includes/platform.html index 6aa88f9208ae..a5653b89d7a8 100644 --- a/docs/_includes/platform.html +++ b/docs/_includes/platform.html @@ -10,9 +10,4 @@

{{ platform.hub-title }}

{% include hub-card.html hub=hub platform=selectedPlatform %} {% endfor %} - -
- - {% include floating-concierge-button.html id="floating-concierge-button-global" %} -
diff --git a/docs/_layouts/default.html b/docs/_layouts/default.html index de3fbc203243..7d98500ecf32 100644 --- a/docs/_layouts/default.html +++ b/docs/_layouts/default.html @@ -57,9 +57,6 @@
{% endif %} - - - {% include floating-concierge-button.html id="floating-concierge-button-lhn" %}
diff --git a/docs/_sass/_main.scss b/docs/_sass/_main.scss index eaaa1c63badb..9276443c3813 100644 --- a/docs/_sass/_main.scss +++ b/docs/_sass/_main.scss @@ -712,41 +712,11 @@ button { } } -#floating-concierge-button-global { - position: fixed; - display: block; - @include breakpoint($breakpoint-tablet) { - display: none; - } -} - -#floating-concierge-button-lhn { - position: absolute; - display: none; - @include breakpoint($breakpoint-tablet) { - display: block; - } -} - .get-help { flex-wrap: wrap; margin-top: 40px; } -.floating-concierge-button { - bottom: 2rem; - right: 2rem; - - img { - width: 4rem; - height: 4rem; - - &:hover { - filter: saturate(2); - } - } -} - .disable-scrollbar { @media screen and (max-width: $breakpoint-tablet) { overflow: hidden; diff --git a/docs/articles/new-expensify/get-paid-back/Referral-Program.md b/docs/articles/new-expensify/get-paid-back/Referral-Program.md index 683e93d0277a..34a35f5dc7c8 100644 --- a/docs/articles/new-expensify/get-paid-back/Referral-Program.md +++ b/docs/articles/new-expensify/get-paid-back/Referral-Program.md @@ -1,53 +1,53 @@ --- -title: Expensify Referral Program -description: Send your joining link, submit a receipt or invoice, and we'll pay you if your referral adopts Expensify. +title: New Expensify Referral Program +description: Share your invite link with a friend, start a chat with a coworker, request money from your boss -- we'll pay you $250 if your referral adopts New Expensify. --- # About -Expensify has grown thanks to our users who love Expensify so much that they tell their friends, colleagues, managers, and fellow business founders to use it, too. +[New Expensify](https://new.expensify.com/) is growing thanks to members like you who love it so much that they tell their friends, family, colleagues, managers, and fellow business founders to use it, too. -As a thank you, every time you bring a new user into the platform who directly or indirectly leads to the adoption of a paid annual plan on Expensify, you will earn $250. +As a thank you, every time you bring a new customer into New Expensify, you'll get $250. Here's how it works. -# How to get paid for referring people to Expensify +# How to get paid to refer anyone to New Expensify -1. Submit a report or invoice, or share your referral link with anyone you know who is spending too much time on expenses, or works at a company that could benefit from using Expensify. +The sky's the limit for this referral program! Your referral can be anyone - a friend, family member, boss, coworker, neighbor, or even social media follower. We're making it as easy as possible to get that cold hard referral $$$. -2. You will get $250 for any referred business that commits to an annual subscription, has 2 or more active users, and makes two monthly payments. +1. There are a bunch of different ways to kick off a referral in New Expensify: + - Start a chat + - Request money + - Send money + - @ mention someone + - Add them to a workspace + +2. You'll get $250 for each referral as long as: + - You're the first to refer them to Expensify + - They start an annual subscription with two or more active users + - They make two payments toward that annual subscription -That’s right! You can refer anyone working at any company you know. - -If their company goes on to become an Expensify customer with an annual subscription, and you are the earliest recorded referrer of a user on that company’s paid Expensify Policy, you'll get paid a referral reward. - -The best way to start is to submit any receipt to your manager (you'll get paid back and set yourself up for $250 if they start a subscription: win-win!) - -Referral rewards for the Spring/Summer 2023 campaign will be paid by direct deposit. +For now, referral rewards will be paid via direct deposit into bank accounts that are connected to Expensify. # FAQ -- **How will I know if I am the first person to refer a company to Expensify?** +- **How will I know if I'm the first person to refer a company to Expensify?** -Successful referrers are notified after their referral pays for 2 months of an annual subscription. We will check for the earliest recorded referrer of a user on the policy, and if that is you, then we will let you know. +Successful referrers are notified after their referral pays for two months of an annual Expensify subscription. We'll check for the earliest recorded referrer of a member on the workspace, and if that's you, we'll let you know. - **How will you pay me if I am successful?** -In the Spring 2023 campaign, Expensify will be paying successful referrers via direct deposit to the Deposit-Only account you have on file. Referral payouts will happen once a month for the duration of the campaign. If you do not have a Deposit-Only account at the time of your referral payout, your deposit will be processed in the next batch. +For now, Expensify will pay successful referrers via direct deposit to the Deposit-Only bank account you have on file. Referral payouts will happen once a month. If you don't have a Deposit-Only bank account connected to Expensify at the time of your referral payout, your deposit will be processed in the next batch. -Learn how to add a Deposit-Only account [here](https://community.expensify.com/discussion/4641/how-to-add-a-deposit-only-bank-account-both-personal-and-business). +Learn how to add a Deposit-Only bank account [here](https://community.expensify.com/discussion/4641/how-to-add-a-deposit-only-bank-account-both-personal-and-business). - **I’m outside of the US, how do I get paid?** -While our referral payouts are in USD, you will be able to get paid via a Wise Borderless account. Learn more [here](https://community.expensify.com/discussion/5940/how-to-get-reimbursed-outside-the-us-with-wise-for-non-us-employees). +While our referral payouts are in USD, you'll be able to get paid via a Wise Borderless account. Learn more [here](https://community.expensify.com/discussion/5940/how-to-get-reimbursed-outside-the-us-with-wise-for-non-us-employees). - **My referral wasn’t counted! How can I appeal?** -Expensify reserves the right to modify the terms of the referral program at any time, and pays out referral bonuses for eligible companies at its own discretion. - -Please send a message to concierge@expensify.com with the billing owner of the company you have referred and our team will review the referral and get back to you. - -- **Where can I find my referral link?** +Expensify reserves the right to modify the terms of the referral program at any time, and pays out referral bonuses for eligible members at its own discretion. If you think there's been a mistake, please send a message to concierge@expensify.com with the email of your referral and our team will review your case. -Expensify members who are opted-in for our newsletters will have received an email containing their unique referral link. +- **Where can I find my referral link?** -On the mobile app, go to **Settings** > **Invite a Friend** > **Share Invite Link** to retrieve your referral link. +In New Expensify, go to **Settings** > **Share code** > **Get $250** to retrieve your invite link. diff --git a/docs/index.html b/docs/index.html index 70bd5f31545a..ceea63cb398a 100644 --- a/docs/index.html +++ b/docs/index.html @@ -12,9 +12,4 @@

{{ site.data.routes.home.title }}

{% include platform-card.html href=platform.platform_href %} {% endfor %}
- -
- - {% include floating-concierge-button.html id="floating-concierge-button-global" %} -
diff --git a/ios/NewExpensify/Info.plist b/ios/NewExpensify/Info.plist index e707e4c590b7..a917b6a4499c 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.11 + 1.4.13 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.11.23 + 1.4.13.0 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index f87a232a7454..b1e7c2a22e26 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.11 + 1.4.13 CFBundleSignature ???? CFBundleVersion - 1.4.11.23 + 1.4.13.0 diff --git a/package-lock.json b/package-lock.json index 63179219c300..86c0950a0b12 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.11-23", + "version": "1.4.13-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.11-23", + "version": "1.4.13-0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -35,7 +35,7 @@ "@react-native-google-signin/google-signin": "^10.0.1", "@react-native-picker/picker": "^2.4.3", "@react-navigation/material-top-tabs": "^6.6.3", - "@react-navigation/native": "6.1.6", + "@react-navigation/native": "6.1.8", "@react-navigation/stack": "6.3.16", "@react-ng/bounds-observer": "^0.2.1", "@rnmapbox/maps": "^10.0.11", @@ -8563,16 +8563,16 @@ } }, "node_modules/@react-navigation/core": { - "version": "6.4.8", - "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.4.8.tgz", - "integrity": "sha512-klZ9Mcf/P2j+5cHMoGyIeurEzyBM2Uq9+NoSFrF6sdV5iCWHLFhrCXuhbBiQ5wVLCKf4lavlkd/DDs47PXs9RQ==", + "version": "6.4.10", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.4.10.tgz", + "integrity": "sha512-oYhqxETRHNHKsipm/BtGL0LI43Hs2VSFoWMbBdHK9OqgQPjTVUitslgLcPpo4zApCcmBWoOLX2qPxhsBda644A==", "dependencies": { - "@react-navigation/routers": "^6.1.8", + "@react-navigation/routers": "^6.1.9", "escape-string-regexp": "^4.0.0", "nanoid": "^3.1.23", "query-string": "^7.1.3", "react-is": "^16.13.0", - "use-latest-callback": "^0.1.5" + "use-latest-callback": "^0.1.7" }, "peerDependencies": { "react": "*" @@ -8608,11 +8608,11 @@ } }, "node_modules/@react-navigation/native": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.6.tgz", - "integrity": "sha512-14PmSy4JR8HHEk04QkxQ0ZLuqtiQfb4BV9kkMXD2/jI4TZ+yc43OnO6fQ2o9wm+Bq8pY3DxyerC2AjNUz+oH7Q==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.8.tgz", + "integrity": "sha512-0alti852nV+8oCVm9H80G6kZvrHoy51+rXBvVCRUs2rNDDozC/xPZs8tyeCJkqdw3cpxZDK8ndXF22uWq28+0Q==", "dependencies": { - "@react-navigation/core": "^6.4.8", + "@react-navigation/core": "^6.4.9", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.1.23" @@ -8623,9 +8623,9 @@ } }, "node_modules/@react-navigation/routers": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.8.tgz", - "integrity": "sha512-CEge+ZLhb1HBrSvv4RwOol7EKLW1QoqVIQlE9TN5MpxS/+VoQvP+cLbuz0Op53/iJfYhtXRFd1ZAd3RTRqto9w==", + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.9.tgz", + "integrity": "sha512-lTM8gSFHSfkJvQkxacGM6VJtBt61ip2XO54aNfswD+KMw6eeZ4oehl7m0me3CR9hnDE4+60iAZR8sAhvCiI3NA==", "dependencies": { "nanoid": "^3.1.23" } @@ -50723,9 +50723,9 @@ } }, "node_modules/use-latest-callback": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.1.6.tgz", - "integrity": "sha512-VO/P91A/PmKH9bcN9a7O3duSuxe6M14ZoYXgA6a8dab8doWNdhiIHzEkX/jFeTTRBsX0Ubk6nG4q2NIjNsj+bg==", + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.1.9.tgz", + "integrity": "sha512-CL/29uS74AwreI/f2oz2hLTW7ZqVeV5+gxFeGudzQrgkCytrHw33G4KbnQOrRlAEzzAFXi7dDLMC9zhWcVpzmw==", "peerDependencies": { "react": ">=16.8" } @@ -58884,16 +58884,16 @@ } }, "@react-navigation/core": { - "version": "6.4.8", - "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.4.8.tgz", - "integrity": "sha512-klZ9Mcf/P2j+5cHMoGyIeurEzyBM2Uq9+NoSFrF6sdV5iCWHLFhrCXuhbBiQ5wVLCKf4lavlkd/DDs47PXs9RQ==", + "version": "6.4.10", + "resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-6.4.10.tgz", + "integrity": "sha512-oYhqxETRHNHKsipm/BtGL0LI43Hs2VSFoWMbBdHK9OqgQPjTVUitslgLcPpo4zApCcmBWoOLX2qPxhsBda644A==", "requires": { - "@react-navigation/routers": "^6.1.8", + "@react-navigation/routers": "^6.1.9", "escape-string-regexp": "^4.0.0", "nanoid": "^3.1.23", "query-string": "^7.1.3", "react-is": "^16.13.0", - "use-latest-callback": "^0.1.5" + "use-latest-callback": "^0.1.7" } }, "@react-navigation/devtools": { @@ -58915,20 +58915,20 @@ } }, "@react-navigation/native": { - "version": "6.1.6", - "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.6.tgz", - "integrity": "sha512-14PmSy4JR8HHEk04QkxQ0ZLuqtiQfb4BV9kkMXD2/jI4TZ+yc43OnO6fQ2o9wm+Bq8pY3DxyerC2AjNUz+oH7Q==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-6.1.8.tgz", + "integrity": "sha512-0alti852nV+8oCVm9H80G6kZvrHoy51+rXBvVCRUs2rNDDozC/xPZs8tyeCJkqdw3cpxZDK8ndXF22uWq28+0Q==", "requires": { - "@react-navigation/core": "^6.4.8", + "@react-navigation/core": "^6.4.9", "escape-string-regexp": "^4.0.0", "fast-deep-equal": "^3.1.3", "nanoid": "^3.1.23" } }, "@react-navigation/routers": { - "version": "6.1.8", - "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.8.tgz", - "integrity": "sha512-CEge+ZLhb1HBrSvv4RwOol7EKLW1QoqVIQlE9TN5MpxS/+VoQvP+cLbuz0Op53/iJfYhtXRFd1ZAd3RTRqto9w==", + "version": "6.1.9", + "resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-6.1.9.tgz", + "integrity": "sha512-lTM8gSFHSfkJvQkxacGM6VJtBt61ip2XO54aNfswD+KMw6eeZ4oehl7m0me3CR9hnDE4+60iAZR8sAhvCiI3NA==", "requires": { "nanoid": "^3.1.23" } @@ -89240,9 +89240,9 @@ } }, "use-latest-callback": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.1.6.tgz", - "integrity": "sha512-VO/P91A/PmKH9bcN9a7O3duSuxe6M14ZoYXgA6a8dab8doWNdhiIHzEkX/jFeTTRBsX0Ubk6nG4q2NIjNsj+bg==", + "version": "0.1.9", + "resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.1.9.tgz", + "integrity": "sha512-CL/29uS74AwreI/f2oz2hLTW7ZqVeV5+gxFeGudzQrgkCytrHw33G4KbnQOrRlAEzzAFXi7dDLMC9zhWcVpzmw==", "requires": {} }, "use-memo-one": { diff --git a/package.json b/package.json index e03beed0bd8e..a3dd3776d125 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.11-23", + "version": "1.4.13-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.", @@ -83,7 +83,7 @@ "@react-native-google-signin/google-signin": "^10.0.1", "@react-native-picker/picker": "^2.4.3", "@react-navigation/material-top-tabs": "^6.6.3", - "@react-navigation/native": "6.1.6", + "@react-navigation/native": "6.1.8", "@react-navigation/stack": "6.3.16", "@react-ng/bounds-observer": "^0.2.1", "@rnmapbox/maps": "^10.0.11", diff --git a/patches/@react-navigation+native+6.1.6.patch b/patches/@react-navigation+native+6.1.8.patch similarity index 90% rename from patches/@react-navigation+native+6.1.6.patch rename to patches/@react-navigation+native+6.1.8.patch index eb933683c850..c461d7e510fe 100644 --- a/patches/@react-navigation+native+6.1.6.patch +++ b/patches/@react-navigation+native+6.1.8.patch @@ -133,7 +133,7 @@ index 0000000..16da117 +//# sourceMappingURL=findFocusedRouteKey.js.map \ No newline at end of file diff --git a/node_modules/@react-navigation/native/lib/module/useLinking.js b/node_modules/@react-navigation/native/lib/module/useLinking.js -index 5bf2a88..a4318ef 100644 +index 6f0ac51..a77b608 100644 --- a/node_modules/@react-navigation/native/lib/module/useLinking.js +++ b/node_modules/@react-navigation/native/lib/module/useLinking.js @@ -2,6 +2,7 @@ import { findFocusedRoute, getActionFromState as getActionFromStateDefault, getP @@ -144,37 +144,7 @@ index 5bf2a88..a4318ef 100644 import ServerContext from './ServerContext'; /** * Find the matching navigation state that changed between 2 navigation states -@@ -34,32 +35,52 @@ const findMatchingState = (a, b) => { - /** - * Run async function in series as it's called. - */ --const series = cb => { -- // Whether we're currently handling a callback -- let handling = false; -- let queue = []; -- const callback = async () => { -- try { -- if (handling) { -- // If we're currently handling a previous event, wait before handling this one -- // Add the callback to the beginning of the queue -- queue.unshift(callback); -- return; -- } -- handling = true; -- await cb(); -- } finally { -- handling = false; -- if (queue.length) { -- // If we have queued items, handle the last one -- const last = queue.pop(); -- last === null || last === void 0 ? void 0 : last(); -- } -- } -+const series = (cb) => { -+ let queue = Promise.resolve(); -+ const callback = () => { -+ queue = queue.then(cb); - }; +@@ -42,6 +43,44 @@ export const series = cb => { return callback; }; let linkingHandlers = []; @@ -219,7 +189,7 @@ index 5bf2a88..a4318ef 100644 export default function useLinking(ref, _ref) { let { independent, -@@ -251,6 +272,9 @@ export default function useLinking(ref, _ref) { +@@ -231,6 +270,9 @@ export default function useLinking(ref, _ref) { // Otherwise it's likely a change triggered by `popstate` path !== pendingPath) { const historyDelta = (focusedState.history ? focusedState.history.length : focusedState.routes.length) - (previousFocusedState.history ? previousFocusedState.history.length : previousFocusedState.routes.length); @@ -229,7 +199,7 @@ index 5bf2a88..a4318ef 100644 if (historyDelta > 0) { // If history length is increased, we should pushState // Note that path might not actually change here, for example, drawer open should pushState -@@ -262,34 +286,55 @@ export default function useLinking(ref, _ref) { +@@ -242,34 +284,55 @@ export default function useLinking(ref, _ref) { // If history length is decreased, i.e. entries were removed, we want to go back const nextIndex = history.backIndex({ diff --git a/src/App.js b/src/App.js index e273dcce1e47..3553900bbc7f 100644 --- a/src/App.js +++ b/src/App.js @@ -8,14 +8,17 @@ import {SafeAreaProvider} from 'react-native-safe-area-context'; import '../wdyr'; import ColorSchemeWrapper from './components/ColorSchemeWrapper'; import ComposeProviders from './components/ComposeProviders'; -import CustomStatusBar from './components/CustomStatusBar'; -import CustomStatusBarContextProvider from './components/CustomStatusBar/CustomStatusBarContextProvider'; +import CustomStatusBarAndBackground from './components/CustomStatusBarAndBackground'; +import CustomStatusBarAndBackgroundContextProvider from './components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContextProvider'; import ErrorBoundary from './components/ErrorBoundary'; import HTMLEngineProvider from './components/HTMLEngineProvider'; import {LocaleContextProvider} from './components/LocaleContextProvider'; import OnyxProvider from './components/OnyxProvider'; import PopoverContextProvider from './components/PopoverProvider'; import SafeArea from './components/SafeArea'; +import ThemeIllustrationsProvider from './components/ThemeIllustrationsProvider'; +import ThemeProvider from './components/ThemeProvider'; +import ThemeStylesProvider from './components/ThemeStylesProvider'; import {CurrentReportIDContextProvider} from './components/withCurrentReportID'; import {EnvironmentProvider} from './components/withEnvironment'; import {KeyboardStateProvider} from './components/withKeyboardState'; @@ -26,9 +29,6 @@ import OnyxUpdateManager from './libs/actions/OnyxUpdateManager'; import * as Session from './libs/actions/Session'; import * as Environment from './libs/Environment/Environment'; import {ReportAttachmentsProvider} from './pages/home/report/ReportAttachmentsContext'; -import ThemeIllustrationsProvider from './styles/illustrations/ThemeIllustrationsProvider'; -import ThemeProvider from './styles/themes/ThemeProvider'; -import ThemeStylesProvider from './styles/ThemeStylesProvider'; // For easier debugging and development, when we are in web we expose Onyx to the window, so you can more easily set data into Onyx if (window && Environment.isDevelopment()) { @@ -68,10 +68,10 @@ function App() { ReportAttachmentsProvider, PickerStateProvider, EnvironmentProvider, - CustomStatusBarContextProvider, + CustomStatusBarAndBackgroundContextProvider, ]} > - + diff --git a/src/CONST.ts b/src/CONST.ts index 072f780b54ae..c0e3d64b5eee 100755 --- a/src/CONST.ts +++ b/src/CONST.ts @@ -255,6 +255,7 @@ const CONST = { BETA_COMMENT_LINKING: 'commentLinking', POLICY_ROOMS: 'policyRooms', VIOLATIONS: 'violations', + REPORT_FIELDS: 'reportFields', }, BUTTON_STATES: { DEFAULT: 'default', @@ -706,9 +707,10 @@ const CONST = { DEFAULT: 'default', }, THEME: { - DEFAULT: 'dark', - LIGHT: 'light', + DEFAULT: 'system', + FALLBACK: 'dark', DARK: 'dark', + LIGHT: 'light', SYSTEM: 'system', }, COLOR_SCHEME: { @@ -2902,6 +2904,10 @@ const CONST = { NAVIGATE: 'NAVIGATE', }, }, + TIME_PERIOD: { + AM: 'AM', + PM: 'PM', + }, INDENTS: ' ', PARENT_CHILD_SEPARATOR: ': ', CATEGORY_LIST_THRESHOLD: 8, @@ -2911,7 +2917,7 @@ const CONST = { SBE: 'SbeDemoSetup', MONEY2020: 'Money2020DemoSetup', }, - + COLON: ':', MAPBOX: { PADDING: 50, DEFAULT_ZOOM: 10, @@ -2957,7 +2963,7 @@ const CONST = { SHARE_CODE: 'shareCode', }, REVENUE: 250, - LEARN_MORE_LINK: 'https://help.expensify.com/articles/new-expensify/billing-and-plan-types/Referral-Program', + LEARN_MORE_LINK: 'https://help.expensify.com/articles/new-expensify/get-paid-back/Referral-Program', LINK: 'https://join.my.expensify.com', }, diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index 933ae678da23..0cc7934ad007 100755 --- a/src/ONYXKEYS.ts +++ b/src/ONYXKEYS.ts @@ -334,10 +334,10 @@ const ONYXKEYS = { WAYPOINT_FORM_DRAFT: 'waypointFormDraft', SETTINGS_STATUS_SET_FORM: 'settingsStatusSetForm', SETTINGS_STATUS_SET_FORM_DRAFT: 'settingsStatusSetFormDraft', - SETTINGS_STATUS_CLEAR_AFTER_FORM: 'settingsStatusClearAfterForm', - SETTINGS_STATUS_CLEAR_AFTER_FORM_DRAFT: 'settingsStatusClearAfterFormDraft', SETTINGS_STATUS_SET_CLEAR_AFTER_FORM: 'settingsStatusSetClearAfterForm', SETTINGS_STATUS_SET_CLEAR_AFTER_FORM_DRAFT: 'settingsStatusSetClearAfterFormDraft', + SETTINGS_STATUS_CLEAR_DATE_FORM: 'settingsStatusClearDateForm', + SETTINGS_STATUS_CLEAR_DATE_FORM_DRAFT: 'settingsStatusClearDateFormDraft', PRIVATE_NOTES_FORM: 'privateNotesForm', PRIVATE_NOTES_FORM_DRAFT: 'privateNotesFormDraft', I_KNOW_A_TEACHER_FORM: 'iKnowTeacherForm', @@ -508,8 +508,8 @@ type OnyxValues = { [ONYXKEYS.FORMS.WAYPOINT_FORM_DRAFT]: OnyxTypes.Form; [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_FORM]: OnyxTypes.Form; [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_FORM_DRAFT]: OnyxTypes.Form; - [ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_AFTER_FORM]: OnyxTypes.Form; - [ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_AFTER_FORM_DRAFT]: OnyxTypes.Form; + [ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_DATE_FORM]: OnyxTypes.Form; + [ONYXKEYS.FORMS.SETTINGS_STATUS_CLEAR_DATE_FORM_DRAFT]: OnyxTypes.Form; [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_CLEAR_AFTER_FORM]: OnyxTypes.Form; [ONYXKEYS.FORMS.SETTINGS_STATUS_SET_CLEAR_AFTER_FORM_DRAFT]: OnyxTypes.Form; [ONYXKEYS.FORMS.PRIVATE_NOTES_FORM]: OnyxTypes.Form; diff --git a/src/ROUTES.ts b/src/ROUTES.ts index 425ff73af56b..ca1fe9f0e81a 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -140,7 +140,9 @@ const ROUTES = { getRoute: (backTo?: string) => getUrlWithBackToParam('settings/security/two-factor-auth', backTo), }, SETTINGS_STATUS: 'settings/profile/status', - SETTINGS_STATUS_SET: 'settings/profile/status/set', + SETTINGS_STATUS_CLEAR_AFTER: 'settings/profile/status/clear-after', + SETTINGS_STATUS_CLEAR_AFTER_DATE: 'settings/profile/status/clear-after/date', + SETTINGS_STATUS_CLEAR_AFTER_TIME: 'settings/profile/status/clear-after/time', KEYBOARD_SHORTCUTS: 'keyboard-shortcuts', diff --git a/src/SCREENS.ts b/src/SCREENS.ts index 921f57953482..2cd263237866 100644 --- a/src/SCREENS.ts +++ b/src/SCREENS.ts @@ -37,8 +37,10 @@ const SCREENS = { CONTACT_METHODS: 'Settings_ContactMethods', CONTACT_METHOD_DETAILS: 'Settings_ContactMethodDetails', NEW_CONTACT_METHOD: 'Settings_NewContactMethod', + STATUS_CLEAR_AFTER: 'Settings_Status_Clear_After', + STATUS_CLEAR_AFTER_DATE: 'Settings_Status_Clear_After_Date', + STATUS_CLEAR_AFTER_TIME: 'Settings_Status_Clear_After_Time', STATUS: 'Settings_Status', - STATUS_SET: 'Settings_Status_Set', PRONOUNS: 'Settings_Pronouns', TIMEZONE: 'Settings_Timezone', TIMEZONE_SELECT: 'Settings_Timezone_Select', diff --git a/src/components/AddPaymentMethodMenu.js b/src/components/AddPaymentMethodMenu.js index 4d01fa108e2a..4abe5655e307 100644 --- a/src/components/AddPaymentMethodMenu.js +++ b/src/components/AddPaymentMethodMenu.js @@ -48,6 +48,9 @@ const propTypes = { /** Currently logged in user accountID */ accountID: PropTypes.number, }), + + /** Whether the personal bank account option should be shown */ + shouldShowPersonalBankAccountOption: PropTypes.bool, }; const defaultProps = { @@ -59,9 +62,10 @@ const defaultProps = { }, anchorRef: () => {}, session: {}, + shouldShowPersonalBankAccountOption: false, }; -function AddPaymentMethodMenu({isVisible, onClose, anchorPosition, anchorAlignment, anchorRef, iouReport, onItemSelected, session}) { +function AddPaymentMethodMenu({isVisible, onClose, anchorPosition, anchorAlignment, anchorRef, iouReport, onItemSelected, session, shouldShowPersonalBankAccountOption}) { const {translate} = useLocalize(); // Users can choose to pay with business bank account in case of Expense reports or in case of P2P IOU report @@ -70,6 +74,8 @@ function AddPaymentMethodMenu({isVisible, onClose, anchorPosition, anchorAlignme ReportUtils.isExpenseReport(iouReport) || (ReportUtils.isIOUReport(iouReport) && !ReportActionsUtils.hasRequestFromCurrentAccount(lodashGet(iouReport, 'reportID', 0), lodashGet(session, 'accountID', 0))); + const canUsePersonalBankAccount = shouldShowPersonalBankAccountOption || ReportUtils.isIOUReport(iouReport); + return ( {}, onKeyPress: () => {}, + style: {}, + containerStyles: {}, }; function AmountTextInput(props) { const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); return ( ); } diff --git a/src/components/AnchorForAttachmentsOnly/index.native.js b/src/components/AnchorForAttachmentsOnly/index.native.js index e3c7a71f2304..3277d51ec058 100644 --- a/src/components/AnchorForAttachmentsOnly/index.native.js +++ b/src/components/AnchorForAttachmentsOnly/index.native.js @@ -1,5 +1,5 @@ import React from 'react'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as anchorForAttachmentsOnlyPropTypes from './anchorForAttachmentsOnlyPropTypes'; import BaseAnchorForAttachmentsOnly from './BaseAnchorForAttachmentsOnly'; diff --git a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.js b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.js index c056f68fa16c..4d32b4427d92 100644 --- a/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.js +++ b/src/components/AnchorForCommentsOnly/BaseAnchorForCommentsOnly.js @@ -7,12 +7,12 @@ import _ from 'underscore'; import PressableWithSecondaryInteraction from '@components/PressableWithSecondaryInteraction'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import * as ContextMenuActions from '@pages/home/report/ContextMenu/ContextMenuActions'; import * as ReportActionContextMenu from '@pages/home/report/ContextMenu/ReportActionContextMenu'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import {propTypes as anchorForCommentsOnlyPropTypes} from './anchorForCommentsOnlyPropTypes'; diff --git a/src/components/AnimatedStep/index.tsx b/src/components/AnimatedStep/index.tsx index 1a87592cba9b..e2b9952c0617 100644 --- a/src/components/AnimatedStep/index.tsx +++ b/src/components/AnimatedStep/index.tsx @@ -1,8 +1,8 @@ import React, {useMemo} from 'react'; import {StyleProp, ViewStyle} from 'react-native'; import * as Animatable from 'react-native-animatable'; +import useThemeStyles from '@hooks/useThemeStyles'; import useNativeDriver from '@libs/useNativeDriver'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ChildrenProps from '@src/types/utils/ChildrenProps'; import {AnimationDirection} from './AnimatedStepContext'; diff --git a/src/components/AnonymousReportFooter.tsx b/src/components/AnonymousReportFooter.tsx index 65dc813a829d..877ca9444661 100644 --- a/src/components/AnonymousReportFooter.tsx +++ b/src/components/AnonymousReportFooter.tsx @@ -3,7 +3,7 @@ import {Text, View} from 'react-native'; import {OnyxCollection} from 'react-native-onyx'; import {OnyxEntry} from 'react-native-onyx/lib/types'; import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as Session from '@userActions/Session'; import {PersonalDetails, Report} from '@src/types/onyx'; import AvatarWithDisplayName from './AvatarWithDisplayName'; diff --git a/src/components/ArchivedReportFooter.tsx b/src/components/ArchivedReportFooter.tsx index 712ef6be769e..7dadd86debfe 100644 --- a/src/components/ArchivedReportFooter.tsx +++ b/src/components/ArchivedReportFooter.tsx @@ -2,10 +2,10 @@ import lodashEscape from 'lodash/escape'; import React from 'react'; import {OnyxEntry, withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as PersonalDetailsUtils from '@libs/PersonalDetailsUtils'; import * as ReportActionsUtils from '@libs/ReportActionsUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import type {PersonalDetailsList, Report, ReportAction} from '@src/types/onyx'; diff --git a/src/components/AttachmentModal.js b/src/components/AttachmentModal.js index 79be536945ac..13b3b9f1e836 100755 --- a/src/components/AttachmentModal.js +++ b/src/components/AttachmentModal.js @@ -8,6 +8,9 @@ import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import compose from '@libs/compose'; @@ -19,9 +22,6 @@ import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import useNativeDriver from '@libs/useNativeDriver'; import reportPropTypes from '@pages/reportPropTypes'; -import useTheme from '@styles/themes/useTheme'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import * as IOU from '@userActions/IOU'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/components/AttachmentPicker/index.native.js b/src/components/AttachmentPicker/index.native.js index 5b955ee69dd3..59928b80c4b1 100644 --- a/src/components/AttachmentPicker/index.native.js +++ b/src/components/AttachmentPicker/index.native.js @@ -12,9 +12,9 @@ import Popover from '@components/Popover'; import useArrowKeyFocusManager from '@hooks/useArrowKeyFocusManager'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as FileUtils from '@libs/fileDownload/FileUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import {defaultProps as baseDefaultProps, propTypes as basePropTypes} from './attachmentPickerPropTypes'; import launchCamera from './launchCamera'; diff --git a/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js index dd2713a38b2b..f4cbffc0e1e4 100644 --- a/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js +++ b/src/components/Attachments/AttachmentCarousel/AttachmentCarouselCellRenderer.js @@ -1,8 +1,8 @@ import PropTypes from 'prop-types'; import React from 'react'; import {PixelRatio, View} from 'react-native'; +import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import useThemeStyles from '@styles/useThemeStyles'; const propTypes = { /** Cell Container styles */ diff --git a/src/components/Attachments/AttachmentCarousel/CarouselButtons.js b/src/components/Attachments/AttachmentCarousel/CarouselButtons.js index 14a6ea268468..1847d30ede22 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselButtons.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselButtons.js @@ -7,9 +7,9 @@ import Button from '@components/Button'; import * as Expensicons from '@components/Icon/Expensicons'; import Tooltip from '@components/Tooltip'; import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import useTheme from '@styles/themes/useTheme'; -import useThemeStyles from '@styles/useThemeStyles'; const propTypes = { /** Where the arrows should be visible */ diff --git a/src/components/Attachments/AttachmentCarousel/CarouselItem.js b/src/components/Attachments/AttachmentCarousel/CarouselItem.js index 1642fa32734a..e1fcc86e12a5 100644 --- a/src/components/Attachments/AttachmentCarousel/CarouselItem.js +++ b/src/components/Attachments/AttachmentCarousel/CarouselItem.js @@ -8,8 +8,8 @@ import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeed import SafeAreaConsumer from '@components/SafeAreaConsumer'; import Text from '@components/Text'; import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import ReportAttachmentsContext from '@pages/home/report/ReportAttachmentsContext'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; const propTypes = { diff --git a/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js b/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js index cc1e20cb44e0..4bce03b6f25e 100644 --- a/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js +++ b/src/components/Attachments/AttachmentCarousel/Pager/ImageTransformer.js @@ -15,7 +15,7 @@ import Animated, { withDecay, withSpring, } from 'react-native-reanimated'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import AttachmentCarouselPagerContext from './AttachmentCarouselPagerContext'; import ImageWrapper from './ImageWrapper'; diff --git a/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js b/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js index b0a8b1f0d083..b639eb291bb1 100644 --- a/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js +++ b/src/components/Attachments/AttachmentCarousel/Pager/ImageWrapper.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {StyleSheet} from 'react-native'; import Animated from 'react-native-reanimated'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; const imageWrapperPropTypes = { children: PropTypes.node.isRequired, diff --git a/src/components/Attachments/AttachmentCarousel/Pager/index.js b/src/components/Attachments/AttachmentCarousel/Pager/index.js index 6913941ed726..15c98ece62cb 100644 --- a/src/components/Attachments/AttachmentCarousel/Pager/index.js +++ b/src/components/Attachments/AttachmentCarousel/Pager/index.js @@ -6,7 +6,7 @@ import PagerView from 'react-native-pager-view'; import Animated, {runOnJS, useAnimatedProps, useAnimatedReaction, useEvent, useHandler, useSharedValue} from 'react-native-reanimated'; import _ from 'underscore'; import refPropTypes from '@components/refPropTypes'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import AttachmentCarouselPagerContext from './AttachmentCarouselPagerContext'; const AnimatedPagerView = Animated.createAnimatedComponent(createNativeWrapper(PagerView)); diff --git a/src/components/Attachments/AttachmentCarousel/index.js b/src/components/Attachments/AttachmentCarousel/index.js index fb31e32de91c..8f225d426dca 100644 --- a/src/components/Attachments/AttachmentCarousel/index.js +++ b/src/components/Attachments/AttachmentCarousel/index.js @@ -6,10 +6,10 @@ import BlockingView from '@components/BlockingViews/BlockingView'; import * as Illustrations from '@components/Icon/Illustrations'; import withLocalize from '@components/withLocalize'; import withWindowDimensions from '@components/withWindowDimensions'; +import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; import Navigation from '@libs/Navigation/Navigation'; -import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/components/Attachments/AttachmentCarousel/index.native.js b/src/components/Attachments/AttachmentCarousel/index.native.js index ea45509d6ce3..374b2d47d12d 100644 --- a/src/components/Attachments/AttachmentCarousel/index.native.js +++ b/src/components/Attachments/AttachmentCarousel/index.native.js @@ -5,9 +5,9 @@ import _ from 'underscore'; import BlockingView from '@components/BlockingViews/BlockingView'; import * as Illustrations from '@components/Icon/Illustrations'; import withLocalize from '@components/withLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; -import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, propTypes} from './attachmentCarouselPropTypes'; diff --git a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js index 3b080e47e4d1..78b69be077aa 100755 --- a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.js @@ -2,8 +2,8 @@ import React, {memo} from 'react'; import ImageView from '@components/ImageView'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import {attachmentViewImageDefaultProps, attachmentViewImagePropTypes} from './propTypes'; diff --git a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js index a61adcf04043..fd709c82a2e2 100755 --- a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js @@ -3,8 +3,8 @@ import AttachmentCarouselPage from '@components/Attachments/AttachmentCarousel/P import ImageView from '@components/ImageView'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import {attachmentViewImageDefaultProps, attachmentViewImagePropTypes} from './propTypes'; diff --git a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.js b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.js index b9dd65e2716b..308d3cf2c0ba 100644 --- a/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.js +++ b/src/components/Attachments/AttachmentView/AttachmentViewPdf/index.android.js @@ -3,7 +3,7 @@ import {StyleSheet, View} from 'react-native'; import {Gesture, GestureDetector} from 'react-native-gesture-handler'; import Animated, {useSharedValue} from 'react-native-reanimated'; import AttachmentCarouselPagerContext from '@components/Attachments/AttachmentCarousel/Pager/AttachmentCarouselPagerContext'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import BaseAttachmentViewPdf from './BaseAttachmentViewPdf'; import {attachmentViewPdfDefaultProps, attachmentViewPdfPropTypes} from './propTypes'; diff --git a/src/components/Attachments/AttachmentView/index.js b/src/components/Attachments/AttachmentView/index.js index 6e1ed651ae06..79d1b6f407b9 100755 --- a/src/components/Attachments/AttachmentView/index.js +++ b/src/components/Attachments/AttachmentView/index.js @@ -12,13 +12,12 @@ import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import useNetwork from '@hooks/useNetwork'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import addEncryptedAuthTokenToURL from '@libs/addEncryptedAuthTokenToURL'; import compose from '@libs/compose'; import * as TransactionUtils from '@libs/TransactionUtils'; -import useTheme from '@styles/themes/useTheme'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; -import cursor from '@styles/utilities/cursor'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; import AttachmentViewImage from './AttachmentViewImage'; @@ -141,7 +140,7 @@ function AttachmentView({ onScaleChanged={onScaleChanged} onToggleKeyboard={onToggleKeyboard} onLoadComplete={() => !loadComplete && setLoadComplete(true)} - errorLabelStyles={isUsedInAttachmentModal ? [styles.textLabel, styles.textLarge] : [cursor.cursorAuto]} + errorLabelStyles={isUsedInAttachmentModal ? [styles.textLabel, styles.textLarge] : [styles.cursorAuto]} style={isUsedInAttachmentModal ? styles.imageModalPDF : styles.flex1} /> diff --git a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx index 07db455968a3..c2320f7c0202 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -4,9 +4,10 @@ import {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'; +import ColorSchemeWrapper from '@components/ColorSchemeWrapper'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import viewForwardedRef from '@src/types/utils/viewForwardedRef'; import type {AutoCompleteSuggestionsProps, RenderSuggestionMenuItemProps} from './types'; @@ -84,18 +85,20 @@ function BaseAutoCompleteSuggestions( style={[styles.autoCompleteSuggestionsContainer, animatedStyles]} exiting={FadeOutDown.duration(100).easing(Easing.inOut(Easing.ease))} > - rowHeight.value} - extraData={highlightedSuggestionIndex} - /> + + rowHeight.value} + extraData={highlightedSuggestionIndex} + /> + ); } diff --git a/src/components/AutoCompleteSuggestions/index.tsx b/src/components/AutoCompleteSuggestions/index.tsx index 3ccbb4efaf5a..baca4011a177 100644 --- a/src/components/AutoCompleteSuggestions/index.tsx +++ b/src/components/AutoCompleteSuggestions/index.tsx @@ -1,9 +1,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; import {View} from 'react-native'; +import useStyleUtils from '@hooks/useStyleUtils'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as DeviceCapabilities from '@libs/DeviceCapabilities'; -import useStyleUtils from '@styles/useStyleUtils'; import BaseAutoCompleteSuggestions from './BaseAutoCompleteSuggestions'; import type {AutoCompleteSuggestionsProps} from './types'; diff --git a/src/components/AutoEmailLink.js b/src/components/AutoEmailLink.js index bffd2493aa5d..af581525ab69 100644 --- a/src/components/AutoEmailLink.js +++ b/src/components/AutoEmailLink.js @@ -2,7 +2,7 @@ import {CONST} from 'expensify-common/lib/CONST'; import PropTypes from 'prop-types'; import React from 'react'; import _ from 'underscore'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import Text from './Text'; import TextLink from './TextLink'; diff --git a/src/components/AutoUpdateTime.js b/src/components/AutoUpdateTime.js index 1970839ec320..b9aa3446fa12 100644 --- a/src/components/AutoUpdateTime.js +++ b/src/components/AutoUpdateTime.js @@ -5,8 +5,8 @@ import PropTypes from 'prop-types'; 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 useThemeStyles from '@styles/useThemeStyles'; import Text from './Text'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; diff --git a/src/components/Avatar.tsx b/src/components/Avatar.tsx index f801cb11e9df..978d2f097cbf 100644 --- a/src/components/Avatar.tsx +++ b/src/components/Avatar.tsx @@ -1,11 +1,11 @@ import React, {useEffect, useState} from 'react'; import {StyleProp, View, ViewStyle} 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 useTheme from '@styles/themes/useTheme'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import type {AvatarSizeName} from '@styles/utils'; import CONST from '@src/CONST'; import {AvatarType} from '@src/types/onyx/OnyxCommon'; diff --git a/src/components/AvatarCropModal/AvatarCropModal.js b/src/components/AvatarCropModal/AvatarCropModal.js index 419891d9bdef..a39daeb78ba7 100644 --- a/src/components/AvatarCropModal/AvatarCropModal.js +++ b/src/components/AvatarCropModal/AvatarCropModal.js @@ -15,11 +15,11 @@ import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import withWindowDimensions, {windowDimensionsPropTypes} from '@components/withWindowDimensions'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import cropOrRotateImage from '@libs/cropOrRotateImage'; -import useTheme from '@styles/themes/useTheme'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ImageCropView from './ImageCropView'; import Slider from './Slider'; diff --git a/src/components/AvatarCropModal/ImageCropView.js b/src/components/AvatarCropModal/ImageCropView.js index 94289b24d6ca..3f6263cf7b95 100644 --- a/src/components/AvatarCropModal/ImageCropView.js +++ b/src/components/AvatarCropModal/ImageCropView.js @@ -5,9 +5,9 @@ import {PanGestureHandler} from 'react-native-gesture-handler'; import Animated, {interpolate, useAnimatedStyle} from 'react-native-reanimated'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import ControlSelection from '@libs/ControlSelection'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import gestureHandlerPropTypes from './gestureHandlerPropTypes'; const propTypes = { diff --git a/src/components/AvatarCropModal/Slider.js b/src/components/AvatarCropModal/Slider.js index 9df6ac3c0498..ba2e1471ce9e 100644 --- a/src/components/AvatarCropModal/Slider.js +++ b/src/components/AvatarCropModal/Slider.js @@ -5,8 +5,8 @@ import {PanGestureHandler} from 'react-native-gesture-handler'; import Animated, {useAnimatedStyle} from 'react-native-reanimated'; import Tooltip from '@components/Tooltip'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import ControlSelection from '@libs/ControlSelection'; -import useThemeStyles from '@styles/useThemeStyles'; import gestureHandlerPropTypes from './gestureHandlerPropTypes'; const propTypes = { diff --git a/src/components/AvatarSkeleton.tsx b/src/components/AvatarSkeleton.tsx index d2706447f756..0887830aa07a 100644 --- a/src/components/AvatarSkeleton.tsx +++ b/src/components/AvatarSkeleton.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {Circle} from 'react-native-svg'; -import useTheme from '@styles/themes/useTheme'; +import useTheme from '@hooks/useTheme'; import SkeletonViewContentLoader from './SkeletonViewContentLoader'; function AvatarSkeleton() { diff --git a/src/components/AvatarWithDisplayName.tsx b/src/components/AvatarWithDisplayName.tsx index 3cc39f9e815f..5ea21502f2ca 100644 --- a/src/components/AvatarWithDisplayName.tsx +++ b/src/components/AvatarWithDisplayName.tsx @@ -2,12 +2,12 @@ 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 useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import * as OptionsListUtils from '@libs/OptionsListUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import useTheme from '@styles/themes/useTheme'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index 9b061ba5c670..325d4706da71 100644 --- a/src/components/AvatarWithImagePicker.js +++ b/src/components/AvatarWithImagePicker.js @@ -4,12 +4,12 @@ import React, {useEffect, useRef, useState} from 'react'; import {StyleSheet, View} from 'react-native'; import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as Browser from '@libs/Browser'; import * as FileUtils from '@libs/fileDownload/FileUtils'; import getImageResolution from '@libs/fileDownload/getImageResolution'; import stylePropTypes from '@styles/stylePropTypes'; -import useTheme from '@styles/themes/useTheme'; -import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import AttachmentModal from './AttachmentModal'; diff --git a/src/components/AvatarWithIndicator.tsx b/src/components/AvatarWithIndicator.tsx index 3ae9507350c8..2fd733d4b072 100644 --- a/src/components/AvatarWithIndicator.tsx +++ b/src/components/AvatarWithIndicator.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {View} from 'react-native'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as UserUtils from '@libs/UserUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import Avatar from './Avatar'; import AvatarSkeleton from './AvatarSkeleton'; import * as Expensicons from './Icon/Expensicons'; diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index b670921dff4c..70aebc30ee83 100644 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -1,7 +1,7 @@ import React, {useCallback} from 'react'; import {GestureResponderEvent, PressableStateCallbackType, StyleProp, TextStyle, View, ViewStyle} from 'react-native'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import Text from './Text'; diff --git a/src/components/Banner.tsx b/src/components/Banner.tsx index 3eb2316aa9d0..7ceb3deb9e63 100644 --- a/src/components/Banner.tsx +++ b/src/components/Banner.tsx @@ -1,9 +1,9 @@ import React, {memo} from 'react'; import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'; import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import Hoverable from './Hoverable'; import Icon from './Icon'; diff --git a/src/components/BaseMiniContextMenuItem.tsx b/src/components/BaseMiniContextMenuItem.tsx index 082c0e20801a..8d115a37cba7 100644 --- a/src/components/BaseMiniContextMenuItem.tsx +++ b/src/components/BaseMiniContextMenuItem.tsx @@ -1,10 +1,10 @@ import React, {ForwardedRef} from 'react'; import {PressableStateCallbackType, View} from 'react-native'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import DomUtils from '@libs/DomUtils'; import getButtonState from '@libs/getButtonState'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import Tooltip from './Tooltip/PopoverAnchorTooltip'; diff --git a/src/components/BigNumberPad.tsx b/src/components/BigNumberPad.tsx index 812e9e78635b..8b840f9d1b57 100644 --- a/src/components/BigNumberPad.tsx +++ b/src/components/BigNumberPad.tsx @@ -1,9 +1,9 @@ import React, {useState} from 'react'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import ControlSelection from '@libs/ControlSelection'; -import useThemeStyles from '@styles/useThemeStyles'; import Button from './Button'; type BigNumberPadProps = { @@ -15,6 +15,9 @@ type BigNumberPadProps = { /** Used to locate this view from native classes. */ id?: string; + + /** Whether long press is disabled */ + isLongPressDisabled: boolean; }; const padNumbers = [ @@ -24,7 +27,7 @@ const padNumbers = [ ['.', '0', '<'], ] as const; -function BigNumberPad({numberPressed, longPressHandlerStateChanged = () => {}, id = 'numPadView'}: BigNumberPadProps) { +function BigNumberPad({numberPressed, longPressHandlerStateChanged = () => {}, id = 'numPadView', isLongPressDisabled = false}: BigNumberPadProps) { const {toLocaleDigit} = useLocalize(); const styles = useThemeStyles(); @@ -85,6 +88,7 @@ function BigNumberPad({numberPressed, longPressHandlerStateChanged = () => {}, i onMouseDown={(e) => { e.preventDefault(); }} + isLongPressDisabled={isLongPressDisabled} /> ); })} diff --git a/src/components/BlockingViews/BlockingView.js b/src/components/BlockingViews/BlockingView.js index aec414cdeb74..44f6b7100509 100644 --- a/src/components/BlockingViews/BlockingView.js +++ b/src/components/BlockingViews/BlockingView.js @@ -6,9 +6,9 @@ import Icon from '@components/Icon'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import useTheme from '@styles/themes/useTheme'; -import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; const propTypes = { diff --git a/src/components/BlockingViews/FullPageNotFoundView.js b/src/components/BlockingViews/FullPageNotFoundView.js index b82474aa0694..ce76b96c0eb0 100644 --- a/src/components/BlockingViews/FullPageNotFoundView.js +++ b/src/components/BlockingViews/FullPageNotFoundView.js @@ -4,8 +4,8 @@ import {View} from 'react-native'; import HeaderWithBackButton from '@components/HeaderWithBackButton'; import * as Illustrations from '@components/Icon/Illustrations'; import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import ROUTES from '@src/ROUTES'; import BlockingView from './BlockingView'; diff --git a/src/components/Button/index.tsx b/src/components/Button/index.tsx index eb99d4b09396..af8ec33683de 100644 --- a/src/components/Button/index.tsx +++ b/src/components/Button/index.tsx @@ -9,9 +9,9 @@ import Text from '@components/Text'; import withNavigationFallback from '@components/withNavigationFallback'; import useActiveElement from '@hooks/useActiveElement'; import useKeyboardShortcut from '@hooks/useKeyboardShortcut'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import HapticFeedback from '@libs/HapticFeedback'; -import useTheme from '@styles/themes/useTheme'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ChildrenProps from '@src/types/utils/ChildrenProps'; import validateSubmitShortcut from './validateSubmitShortcut'; @@ -106,6 +106,9 @@ type ButtonProps = (ButtonWithText | ChildrenProps) & { /** Should enable the haptic feedback? */ shouldEnableHapticFeedback?: boolean; + /** Should disable the long press? */ + isLongPressDisabled?: boolean; + /** Id to use for this button */ id?: string; @@ -149,6 +152,7 @@ function Button( shouldRemoveRightBorderRadius = false, shouldRemoveLeftBorderRadius = false, shouldEnableHapticFeedback = false, + isLongPressDisabled = false, id = '', accessibilityLabel = '', @@ -255,6 +259,9 @@ function Button( return onPress(event); }} onLongPress={(event) => { + if (isLongPressDisabled) { + return; + } if (shouldEnableHapticFeedback) { HapticFeedback.longPress(); } diff --git a/src/components/ButtonWithDropdownMenu.js b/src/components/ButtonWithDropdownMenu.js index a5f311740f19..0291143a9dbc 100644 --- a/src/components/ButtonWithDropdownMenu.js +++ b/src/components/ButtonWithDropdownMenu.js @@ -2,10 +2,10 @@ import PropTypes from 'prop-types'; import React, {useEffect, useRef, useState} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; -import useTheme from '@styles/themes/useTheme'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import Button from './Button'; import Icon from './Icon'; diff --git a/src/components/CardPreview.tsx b/src/components/CardPreview.tsx index 6dc8abfb80ef..e3ddbd54b892 100644 --- a/src/components/CardPreview.tsx +++ b/src/components/CardPreview.tsx @@ -3,7 +3,7 @@ import {View} from 'react-native'; import {OnyxEntry, withOnyx} from 'react-native-onyx'; import ExpensifyCardImage from '@assets/images/expensify-card.svg'; import usePrivatePersonalDetails from '@hooks/usePrivatePersonalDetails'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import ONYXKEYS from '@src/ONYXKEYS'; import {PrivatePersonalDetails, Session} from '@src/types/onyx'; diff --git a/src/components/CategoryPicker/index.js b/src/components/CategoryPicker/index.js index 36cf9b1deadc..d170def12276 100644 --- a/src/components/CategoryPicker/index.js +++ b/src/components/CategoryPicker/index.js @@ -4,8 +4,8 @@ import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import OptionsSelector from '@components/OptionsSelector'; import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as OptionsListUtils from '@libs/OptionsListUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; import {defaultProps, propTypes} from './categoryPickerPropTypes'; diff --git a/src/components/Checkbox.tsx b/src/components/Checkbox.tsx index 5dd3164eadcc..23bc068e8fe0 100644 --- a/src/components/Checkbox.tsx +++ b/src/components/Checkbox.tsx @@ -1,8 +1,8 @@ import React, {ForwardedRef, forwardRef, KeyboardEvent as ReactKeyboardEvent} from 'react'; import {GestureResponderEvent, StyleProp, View, ViewStyle} from 'react-native'; -import useTheme from '@styles/themes/useTheme'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; +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 Icon from './Icon'; diff --git a/src/components/CheckboxWithLabel.js b/src/components/CheckboxWithLabel.js index 92cd7ea38eea..24f61c305dda 100644 --- a/src/components/CheckboxWithLabel.js +++ b/src/components/CheckboxWithLabel.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React, {useState} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import Checkbox from './Checkbox'; import FormHelpMessage from './FormHelpMessage'; diff --git a/src/components/CollapsibleSection/index.tsx b/src/components/CollapsibleSection/index.tsx index 04574c5fd057..bc7bf6e89dba 100644 --- a/src/components/CollapsibleSection/index.tsx +++ b/src/components/CollapsibleSection/index.tsx @@ -4,7 +4,7 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import Text from '@components/Text'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import ChildrenProps from '@src/types/utils/ChildrenProps'; import Collapsible from './Collapsible'; diff --git a/src/components/ColorSchemeWrapper/index.tsx b/src/components/ColorSchemeWrapper/index.tsx index 2909f1ffbe9f..0a1ccc5e5d67 100644 --- a/src/components/ColorSchemeWrapper/index.tsx +++ b/src/components/ColorSchemeWrapper/index.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {View} from 'react-native'; -import useTheme from '@styles/themes/useTheme'; -import useThemeStyles from '@styles/useThemeStyles'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; function ColorSchemeWrapper({children}: React.PropsWithChildren): React.ReactElement { const theme = useTheme(); diff --git a/src/components/CommunicationsLink.js b/src/components/CommunicationsLink.js index dbbe5737b3aa..01ae0354a66d 100644 --- a/src/components/CommunicationsLink.js +++ b/src/components/CommunicationsLink.js @@ -1,8 +1,8 @@ import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; +import useThemeStyles from '@hooks/useThemeStyles'; import Clipboard from '@libs/Clipboard'; -import useThemeStyles from '@styles/useThemeStyles'; import ContextMenuItem from './ContextMenuItem'; import * as Expensicons from './Icon/Expensicons'; import withLocalize, {withLocalizePropTypes} from './withLocalize'; diff --git a/src/components/Composer/index.android.js b/src/components/Composer/index.android.js index 698c68cc78e9..af64831df117 100644 --- a/src/components/Composer/index.android.js +++ b/src/components/Composer/index.android.js @@ -3,9 +3,9 @@ import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {StyleSheet} from 'react-native'; import _ from 'underscore'; import RNTextInput from '@components/RNTextInput'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as ComposerUtils from '@libs/ComposerUtils'; -import useTheme from '@styles/themes/useTheme'; -import useThemeStyles from '@styles/useThemeStyles'; const propTypes = { /** Maximum number of lines in the text input */ diff --git a/src/components/Composer/index.ios.js b/src/components/Composer/index.ios.js index 9852e607562b..c9947999b273 100644 --- a/src/components/Composer/index.ios.js +++ b/src/components/Composer/index.ios.js @@ -3,9 +3,9 @@ import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {StyleSheet} from 'react-native'; import _ from 'underscore'; import RNTextInput from '@components/RNTextInput'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as ComposerUtils from '@libs/ComposerUtils'; -import useTheme from '@styles/themes/useTheme'; -import useThemeStyles from '@styles/useThemeStyles'; const propTypes = { /** If the input should clear, it actually gets intercepted instead of .clear() */ diff --git a/src/components/Composer/index.js b/src/components/Composer/index.js index 4bb3df5c1b85..3af22b63ed69 100755 --- a/src/components/Composer/index.js +++ b/src/components/Composer/index.js @@ -8,6 +8,10 @@ import RNTextInput from '@components/RNTextInput'; import Text from '@components/Text'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; import withNavigation from '@components/withNavigation'; +import useIsScrollBarVisible from '@hooks/useIsScrollBarVisible'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as Browser from '@libs/Browser'; import compose from '@libs/compose'; @@ -15,9 +19,6 @@ import * as ComposerUtils from '@libs/ComposerUtils'; import updateIsFullComposerAvailable from '@libs/ComposerUtils/updateIsFullComposerAvailable'; import isEnterWhileComposition from '@libs/KeyboardShortcut/isEnterWhileComposition'; import ReportActionComposeFocusManager from '@libs/ReportActionComposeFocusManager'; -import useTheme from '@styles/themes/useTheme'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; const propTypes = { @@ -86,6 +87,9 @@ const propTypes = { /** Whether the sull composer is open */ isComposerFullSize: PropTypes.bool, + /** Should make the input only scroll inside the element avoid scroll out to parent */ + shouldContainScroll: PropTypes.bool, + ...withLocalizePropTypes, }; @@ -113,6 +117,7 @@ const defaultProps = { checkComposerVisibility: () => false, isReportActionCompose: false, isComposerFullSize: false, + shouldContainScroll: false, }; /** @@ -164,6 +169,7 @@ function Composer({ selection: selectionProp, isReportActionCompose, isComposerFullSize, + shouldContainScroll, ...props }) { const theme = useTheme(); @@ -180,6 +186,7 @@ function Composer({ const [caretContent, setCaretContent] = useState(''); const [valueBeforeCaret, setValueBeforeCaret] = useState(''); const [textInputWidth, setTextInputWidth] = useState(''); + const isScrollBarVisible = useIsScrollBarVisible(textInput, value); useEffect(() => { if (!shouldClear) { @@ -418,18 +425,26 @@ function Composer({ ); - const inputStyleMemo = useMemo( - () => [ + const scrollStyleMemo = useMemo(() => { + if (shouldContainScroll) { + return isScrollBarVisible ? [styles.overflowScroll, styles.overscrollBehaviorContain] : styles.overflowHidden; + } + return [ // We are hiding the scrollbar to prevent it from reducing the text input width, // so we can get the correct scroll height while calculating the number of lines. numberOfLines < maxLines ? styles.overflowHidden : {}, + ]; + }, [shouldContainScroll, isScrollBarVisible, maxLines, numberOfLines, styles.overflowHidden, styles.overflowScroll, styles.overscrollBehaviorContain]); + const inputStyleMemo = useMemo( + () => [ StyleSheet.flatten([style, {outline: 'none'}]), StyleUtils.getComposeTextAreaPadding(numberOfLines, isComposerFullSize), Browser.isMobileSafari() || Browser.isSafari() ? styles.rtlTextRenderForSafari : {}, + scrollStyleMemo, ], - [numberOfLines, maxLines, styles.overflowHidden, styles.rtlTextRenderForSafari, style, StyleUtils, isComposerFullSize], + [numberOfLines, scrollStyleMemo, styles.rtlTextRenderForSafari, style, StyleUtils, isComposerFullSize], ); return ( diff --git a/src/components/ConfirmContent.js b/src/components/ConfirmContent.js index ff8ee4f861a4..8574cc02014e 100644 --- a/src/components/ConfirmContent.js +++ b/src/components/ConfirmContent.js @@ -4,7 +4,7 @@ import {View} from 'react-native'; import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import Button from './Button'; import Header from './Header'; diff --git a/src/components/ConfirmationPage.tsx b/src/components/ConfirmationPage.tsx index 12e8b40a0f25..21813edd693d 100644 --- a/src/components/ConfirmationPage.tsx +++ b/src/components/ConfirmationPage.tsx @@ -1,6 +1,6 @@ import React from 'react'; import {View} from 'react-native'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import Button from './Button'; import FixedFooter from './FixedFooter'; import Lottie from './Lottie'; diff --git a/src/components/ConfirmedRoute.js b/src/components/ConfirmedRoute.js index 79b97b38194a..3abec6d0d1f4 100644 --- a/src/components/ConfirmedRoute.js +++ b/src/components/ConfirmedRoute.js @@ -5,9 +5,9 @@ import React, {useCallback, useEffect} from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import useNetwork from '@hooks/useNetwork'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as TransactionUtils from '@libs/TransactionUtils'; -import useTheme from '@styles/themes/useTheme'; -import useThemeStyles from '@styles/useThemeStyles'; import * as MapboxToken from '@userActions/MapboxToken'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/components/ConnectBankAccountButton.js b/src/components/ConnectBankAccountButton.js index 6afd3d57d4e6..f036918d9429 100644 --- a/src/components/ConnectBankAccountButton.js +++ b/src/components/ConnectBankAccountButton.js @@ -1,9 +1,9 @@ import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; +import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; -import useThemeStyles from '@styles/useThemeStyles'; import * as ReimbursementAccount from '@userActions/ReimbursementAccount'; import Button from './Button'; import * as Expensicons from './Icon/Expensicons'; diff --git a/src/components/ContextMenuItem.js b/src/components/ContextMenuItem.js index 2cabd71b11cb..307cfcde9b10 100644 --- a/src/components/ContextMenuItem.js +++ b/src/components/ContextMenuItem.js @@ -1,10 +1,10 @@ import PropTypes from 'prop-types'; import React, {forwardRef, useImperativeHandle} from 'react'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import useThrottledButtonState from '@hooks/useThrottledButtonState'; import useWindowDimensions from '@hooks/useWindowDimensions'; import getButtonState from '@libs/getButtonState'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import BaseMiniContextMenuItem from './BaseMiniContextMenuItem'; import Icon from './Icon'; import MenuItem from './MenuItem'; diff --git a/src/components/CopyTextToClipboard.js b/src/components/CopyTextToClipboard.js deleted file mode 100644 index 678537c6a3d7..000000000000 --- a/src/components/CopyTextToClipboard.js +++ /dev/null @@ -1,48 +0,0 @@ -import PropTypes from 'prop-types'; -import React, {useCallback} from 'react'; -import Clipboard from '@libs/Clipboard'; -import * as Expensicons from './Icon/Expensicons'; -import PressableWithDelayToggle from './Pressable/PressableWithDelayToggle'; -import withLocalize, {withLocalizePropTypes} from './withLocalize'; - -const propTypes = { - /** The text to display and copy to the clipboard */ - text: PropTypes.string.isRequired, - - /** Styles to apply to the text */ - // eslint-disable-next-line react/forbid-prop-types - textStyles: PropTypes.arrayOf(PropTypes.object), - urlToCopy: PropTypes.string, - accessibilityRole: PropTypes.string, - ...withLocalizePropTypes, -}; - -const defaultProps = { - textStyles: [], - urlToCopy: null, - accessibilityRole: undefined, -}; - -function CopyTextToClipboard(props) { - const copyToClipboard = useCallback(() => { - Clipboard.setString(props.urlToCopy || props.text); - }, [props.text, props.urlToCopy]); - - return ( - - ); -} - -CopyTextToClipboard.propTypes = propTypes; -CopyTextToClipboard.defaultProps = defaultProps; -CopyTextToClipboard.displayName = 'CopyTextToClipboard'; - -export default withLocalize(CopyTextToClipboard); diff --git a/src/components/CopyTextToClipboard.tsx b/src/components/CopyTextToClipboard.tsx new file mode 100644 index 000000000000..6f3b42e88fee --- /dev/null +++ b/src/components/CopyTextToClipboard.tsx @@ -0,0 +1,45 @@ +import React, {useCallback} from 'react'; +import {AccessibilityRole, StyleProp, TextStyle} from 'react-native'; +import useLocalize from '@hooks/useLocalize'; +import Clipboard from '@libs/Clipboard'; +import * as Expensicons from './Icon/Expensicons'; +import PressableWithDelayToggle from './Pressable/PressableWithDelayToggle'; + +type CopyTextToClipboardProps = { + /** The text to display and copy to the clipboard */ + text: string; + + /** Styles to apply to the text */ + textStyles?: StyleProp; + + urlToCopy?: string; + + accessibilityRole?: AccessibilityRole; +}; + +function CopyTextToClipboard({text, textStyles, urlToCopy, accessibilityRole}: CopyTextToClipboardProps) { + const {translate} = useLocalize(); + + const copyToClipboard = useCallback(() => { + // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- nullish coalescing doesn't achieve the same result in this case + Clipboard.setString(urlToCopy || text); + }, [text, urlToCopy]); + + return ( + + ); +} + +CopyTextToClipboard.displayName = 'CopyTextToClipboard'; + +export default CopyTextToClipboard; diff --git a/src/components/CountrySelector.js b/src/components/CountrySelector.js index b138bc949937..68a6486bce48 100644 --- a/src/components/CountrySelector.js +++ b/src/components/CountrySelector.js @@ -2,8 +2,8 @@ import PropTypes from 'prop-types'; import React, {useEffect} from 'react'; import {View} from 'react-native'; import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; -import useThemeStyles from '@styles/useThemeStyles'; import ROUTES from '@src/ROUTES'; import FormHelpMessage from './FormHelpMessage'; import MenuItemWithTopDescription from './MenuItemWithTopDescription'; diff --git a/src/components/CurrencySymbolButton.js b/src/components/CurrencySymbolButton.js index 47c25a43ad11..d03834fc1fd6 100644 --- a/src/components/CurrencySymbolButton.js +++ b/src/components/CurrencySymbolButton.js @@ -1,7 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import PressableWithoutFeedback from './Pressable/PressableWithoutFeedback'; import Text from './Text'; diff --git a/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx b/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx index 6f6daddc51b8..02c308705994 100644 --- a/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx +++ b/src/components/CurrentUserPersonalDetailsSkeletonView/index.tsx @@ -3,9 +3,9 @@ import {View} from 'react-native'; import {Circle, Rect} from 'react-native-svg'; import {ValueOf} from 'type-fest'; import SkeletonViewContentLoader from '@components/SkeletonViewContentLoader'; -import useTheme from '@styles/themes/useTheme'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; diff --git a/src/components/CurrentWalletBalance.tsx b/src/components/CurrentWalletBalance.tsx index 9a5b2cd0227b..28a83fb1ae50 100644 --- a/src/components/CurrentWalletBalance.tsx +++ b/src/components/CurrentWalletBalance.tsx @@ -1,8 +1,8 @@ import React from 'react'; import {StyleProp, TextStyle} from 'react-native'; import {OnyxEntry, withOnyx} from 'react-native-onyx'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import ONYXKEYS from '@src/ONYXKEYS'; import UserWallet from '@src/types/onyx/UserWallet'; import Text from './Text'; diff --git a/src/components/CustomStatusBar/CustomStatusBarContext.tsx b/src/components/CustomStatusBar/CustomStatusBarContext.tsx deleted file mode 100644 index b2c317b05c25..000000000000 --- a/src/components/CustomStatusBar/CustomStatusBarContext.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import {createContext} from 'react'; - -type CustomStatusBarContextType = { - isRootStatusBarDisabled: boolean; - disableRootStatusBar: (isDisabled: boolean) => void; -}; - -const CustomStatusBarContext = createContext({isRootStatusBarDisabled: false, disableRootStatusBar: () => undefined}); - -export default CustomStatusBarContext; -export {type CustomStatusBarContextType}; diff --git a/src/components/CustomStatusBar/CustomStatusBarContextProvider.tsx b/src/components/CustomStatusBar/CustomStatusBarContextProvider.tsx deleted file mode 100644 index 27a5cac5d8cc..000000000000 --- a/src/components/CustomStatusBar/CustomStatusBarContextProvider.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React, {useMemo, useState} from 'react'; -import CustomStatusBarContext from './CustomStatusBarContext'; - -function CustomStatusBarContextProvider({children}: React.PropsWithChildren) { - const [isRootStatusBarDisabled, disableRootStatusBar] = useState(false); - const value = useMemo( - () => ({ - isRootStatusBarDisabled, - disableRootStatusBar, - }), - [isRootStatusBarDisabled], - ); - - return {children}; -} - -export default CustomStatusBarContextProvider; diff --git a/src/components/CustomStatusBar/index.tsx b/src/components/CustomStatusBar/index.tsx deleted file mode 100644 index 3b5022c60898..000000000000 --- a/src/components/CustomStatusBar/index.tsx +++ /dev/null @@ -1,91 +0,0 @@ -import {EventListenerCallback, NavigationContainerEventMap} from '@react-navigation/native'; -import PropTypes from 'prop-types'; -import React, {useCallback, useContext, useEffect} from 'react'; -import {navigationRef} from '@libs/Navigation/Navigation'; -import StatusBar from '@libs/StatusBar'; -import useTheme from '@styles/themes/useTheme'; -import CustomStatusBarContext from './CustomStatusBarContext'; -import updateStatusBarAppearance from './updateStatusBarAppearance'; - -type CustomStatusBarProps = { - isNested: boolean; -}; - -const propTypes = { - /** Whether the CustomStatusBar is nested within another CustomStatusBar. - * A nested CustomStatusBar will disable the "root" CustomStatusBar. */ - isNested: PropTypes.bool, -}; - -type CustomStatusBarType = { - (props: CustomStatusBarProps): React.ReactNode; - displayName: string; - propTypes: typeof propTypes; -}; - -// eslint-disable-next-line react/function-component-definition -const CustomStatusBar: CustomStatusBarType = ({isNested = false}) => { - const {isRootStatusBarDisabled, disableRootStatusBar} = useContext(CustomStatusBarContext); - const theme = useTheme(); - - const isDisabled = !isNested && isRootStatusBarDisabled; - - useEffect(() => { - if (isNested) { - disableRootStatusBar(true); - } - - return () => { - if (!isNested) { - return; - } - disableRootStatusBar(false); - }; - }, [disableRootStatusBar, isNested]); - - const updateStatusBarStyle = useCallback>(() => { - if (isDisabled) { - return; - } - - // Set the status bar colour depending on the current route. - // If we don't have any colour defined for a route, fall back to - // appBG color. - const currentRoute = navigationRef.getCurrentRoute(); - - let currentScreenBackgroundColor = theme.appBG; - let statusBarStyle = theme.statusBarStyle; - if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_THEMES) { - const screenTheme = theme.PAGE_THEMES[currentRoute.name]; - currentScreenBackgroundColor = screenTheme.backgroundColor; - statusBarStyle = screenTheme.statusBarStyle; - } - - updateStatusBarAppearance({backgroundColor: currentScreenBackgroundColor, statusBarStyle}); - }, [isDisabled, theme.PAGE_THEMES, theme.appBG, theme.statusBarStyle]); - - useEffect(() => { - navigationRef.addListener('state', updateStatusBarStyle); - - return () => navigationRef.removeListener('state', updateStatusBarStyle); - }, [updateStatusBarStyle]); - - useEffect(() => { - if (isDisabled) { - return; - } - - updateStatusBarAppearance({statusBarStyle: theme.statusBarStyle}); - }, [isDisabled, theme.statusBarStyle]); - - if (isDisabled) { - return null; - } - - return ; -}; - -CustomStatusBar.displayName = 'CustomStatusBar'; -CustomStatusBar.propTypes = propTypes; - -export default CustomStatusBar; diff --git a/src/components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext.tsx b/src/components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext.tsx new file mode 100644 index 000000000000..4a1a1cd2f964 --- /dev/null +++ b/src/components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContext.tsx @@ -0,0 +1,11 @@ +import {createContext} from 'react'; + +type CustomStatusBarAndBackgroundContextType = { + isRootStatusBarDisabled: boolean; + disableRootStatusBar: (isDisabled: boolean) => void; +}; + +const CustomStatusBarAndBackgroundContext = createContext({isRootStatusBarDisabled: false, disableRootStatusBar: () => undefined}); + +export default CustomStatusBarAndBackgroundContext; +export {type CustomStatusBarAndBackgroundContextType}; diff --git a/src/components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContextProvider.tsx b/src/components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContextProvider.tsx new file mode 100644 index 000000000000..b4d553b60d0f --- /dev/null +++ b/src/components/CustomStatusBarAndBackground/CustomStatusBarAndBackgroundContextProvider.tsx @@ -0,0 +1,17 @@ +import React, {useMemo, useState} from 'react'; +import CustomStatusBarAndBackgroundContext from './CustomStatusBarAndBackgroundContext'; + +function CustomStatusBarAndBackgroundContextProvider({children}: React.PropsWithChildren) { + const [isRootStatusBarDisabled, disableRootStatusBar] = useState(false); + const value = useMemo( + () => ({ + isRootStatusBarDisabled, + disableRootStatusBar, + }), + [isRootStatusBarDisabled], + ); + + return {children}; +} + +export default CustomStatusBarAndBackgroundContextProvider; diff --git a/src/components/CustomStatusBarAndBackground/index.tsx b/src/components/CustomStatusBarAndBackground/index.tsx new file mode 100644 index 000000000000..b84f9c6a6630 --- /dev/null +++ b/src/components/CustomStatusBarAndBackground/index.tsx @@ -0,0 +1,113 @@ +import React, {useCallback, useContext, useEffect, useRef, useState} from 'react'; +import useTheme from '@hooks/useTheme'; +import {navigationRef} from '@libs/Navigation/Navigation'; +import StatusBar from '@libs/StatusBar'; +import CustomStatusBarAndBackgroundContext from './CustomStatusBarAndBackgroundContext'; +import updateGlobalBackgroundColor from './updateGlobalBackgroundColor'; +import updateStatusBarAppearance from './updateStatusBarAppearance'; + +type CustomStatusBarAndBackgroundProps = { + /** Whether the CustomStatusBar is nested within another CustomStatusBar. + * A nested CustomStatusBar will disable the "root" CustomStatusBar. */ + isNested: boolean; +}; + +function CustomStatusBarAndBackground({isNested = false}: CustomStatusBarAndBackgroundProps) { + const {isRootStatusBarDisabled, disableRootStatusBar} = useContext(CustomStatusBarAndBackgroundContext); + const theme = useTheme(); + const [statusBarStyle, setStatusBarStyle] = useState(theme.statusBarStyle); + + const isDisabled = !isNested && isRootStatusBarDisabled; + + // Disable the root status bar when a nested status bar is rendered + useEffect(() => { + if (isNested) { + disableRootStatusBar(true); + } + + return () => { + if (!isNested) { + return; + } + disableRootStatusBar(false); + }; + }, [disableRootStatusBar, isNested]); + + const listenerCount = useRef(0); + const updateStatusBarStyle = useCallback( + (listenerId?: number) => { + // Check if this function is either called through the current navigation listener or the general useEffect which listens for theme changes. + if (listenerId !== undefined && listenerId !== listenerCount.current) { + return; + } + + // Set the status bar colour depending on the current route. + // If we don't have any colour defined for a route, fall back to + // appBG color. + let currentRoute: ReturnType | undefined; + if (navigationRef.isReady()) { + currentRoute = navigationRef.getCurrentRoute(); + } + + let currentScreenBackgroundColor = theme.appBG; + let newStatusBarStyle = theme.statusBarStyle; + if (currentRoute && 'name' in currentRoute && currentRoute.name in theme.PAGE_THEMES) { + const screenTheme = theme.PAGE_THEMES[currentRoute.name]; + currentScreenBackgroundColor = screenTheme.backgroundColor; + newStatusBarStyle = screenTheme.statusBarStyle; + } + + // Don't update the status bar style if it's the same as the current one, to prevent flashing. + if (newStatusBarStyle === statusBarStyle) { + updateStatusBarAppearance({backgroundColor: currentScreenBackgroundColor}); + } else { + updateStatusBarAppearance({backgroundColor: currentScreenBackgroundColor, statusBarStyle: newStatusBarStyle}); + setStatusBarStyle(newStatusBarStyle); + } + }, + [statusBarStyle, theme.PAGE_THEMES, theme.appBG, theme.statusBarStyle], + ); + + // Add navigation state listeners to update the status bar every time the route changes + // We have to pass a count as the listener id, because "react-navigation" somehow doesn't remove listeners properyl + useEffect(() => { + if (isDisabled) { + return; + } + + const listenerId = ++listenerCount.current; + const listener = () => updateStatusBarStyle(listenerId); + + navigationRef.addListener('state', listener); + return () => navigationRef.removeListener('state', listener); + }, [isDisabled, theme.appBG, updateStatusBarStyle]); + + // Update the status bar style everytime the theme changes + useEffect(() => { + if (isDisabled) { + return; + } + + updateStatusBarStyle(); + }, [isDisabled, theme, updateStatusBarStyle]); + + // Update the global background (on web) everytime the theme changes. + // The background of the html element needs to be updated, otherwise you will see a big contrast when resizing the window or when the keyboard is open on iOS web. + useEffect(() => { + if (isDisabled) { + return; + } + + updateGlobalBackgroundColor(theme); + }, [isDisabled, theme]); + + if (isDisabled) { + return null; + } + + return ; +} + +CustomStatusBarAndBackground.displayName = 'CustomStatusBarAndBackground'; + +export default CustomStatusBarAndBackground; diff --git a/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/index.ts b/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/index.ts new file mode 100644 index 000000000000..dac994ba8597 --- /dev/null +++ b/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/index.ts @@ -0,0 +1,5 @@ +import type UpdateGlobalBackgroundColor from './types'; + +const updateGlobalBackgroundColor: UpdateGlobalBackgroundColor = () => undefined; + +export default updateGlobalBackgroundColor; diff --git a/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/index.website.ts b/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/index.website.ts new file mode 100644 index 000000000000..481d866dbe4f --- /dev/null +++ b/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/index.website.ts @@ -0,0 +1,8 @@ +import UpdateGlobalBackgroundColor from './types'; + +const updateGlobalBackgroundColor: UpdateGlobalBackgroundColor = (theme) => { + const htmlElement = document.getElementsByTagName('html')[0]; + htmlElement.style.setProperty('background-color', theme.appBG); +}; + +export default updateGlobalBackgroundColor; diff --git a/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/types.ts b/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/types.ts new file mode 100644 index 000000000000..83bd36a9428a --- /dev/null +++ b/src/components/CustomStatusBarAndBackground/updateGlobalBackgroundColor/types.ts @@ -0,0 +1,5 @@ +import {ThemeColors} from '@styles/theme/types'; + +type UpdateGlobalBackgroundColor = (theme: ThemeColors) => void; + +export default UpdateGlobalBackgroundColor; diff --git a/src/components/CustomStatusBar/updateStatusBarAppearance/index.android.ts b/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.android.ts similarity index 100% rename from src/components/CustomStatusBar/updateStatusBarAppearance/index.android.ts rename to src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.android.ts diff --git a/src/components/CustomStatusBar/updateStatusBarAppearance/index.ios.ts b/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.ios.ts similarity index 100% rename from src/components/CustomStatusBar/updateStatusBarAppearance/index.ios.ts rename to src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.ios.ts diff --git a/src/components/CustomStatusBar/updateStatusBarAppearance/index.ts b/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.ts similarity index 100% rename from src/components/CustomStatusBar/updateStatusBarAppearance/index.ts rename to src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/index.ts diff --git a/src/components/CustomStatusBar/updateStatusBarAppearance/types.ts b/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/types.ts similarity index 77% rename from src/components/CustomStatusBar/updateStatusBarAppearance/types.ts rename to src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/types.ts index 3d16b5944a31..823f0059eccf 100644 --- a/src/components/CustomStatusBar/updateStatusBarAppearance/types.ts +++ b/src/components/CustomStatusBarAndBackground/updateStatusBarAppearance/types.ts @@ -1,4 +1,4 @@ -import {StatusBarStyle} from '@styles/styles'; +import {StatusBarStyle} from '@styles/index'; type UpdateStatusBarAppearanceProps = { backgroundColor?: string; diff --git a/src/components/DatePicker/CalendarPicker/ArrowIcon.js b/src/components/DatePicker/CalendarPicker/ArrowIcon.js index a03e18085706..793aca9e7635 100644 --- a/src/components/DatePicker/CalendarPicker/ArrowIcon.js +++ b/src/components/DatePicker/CalendarPicker/ArrowIcon.js @@ -3,8 +3,8 @@ import React from 'react'; import {View} from 'react-native'; import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; const propTypes = { diff --git a/src/components/DatePicker/CalendarPicker/YearPickerModal.js b/src/components/DatePicker/CalendarPicker/YearPickerModal.js index f51d869efbac..e8883648051c 100644 --- a/src/components/DatePicker/CalendarPicker/YearPickerModal.js +++ b/src/components/DatePicker/CalendarPicker/YearPickerModal.js @@ -7,7 +7,7 @@ import ScreenWrapper from '@components/ScreenWrapper'; import SelectionList from '@components/SelectionList'; import {radioListItemPropTypes} from '@components/SelectionList/selectionListPropTypes'; import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; const propTypes = { diff --git a/src/components/DatePicker/index.js b/src/components/DatePicker/index.js index ac6454d25975..8af550c9dc66 100644 --- a/src/components/DatePicker/index.js +++ b/src/components/DatePicker/index.js @@ -8,7 +8,7 @@ import * as Expensicons from '@components/Icon/Expensicons'; import TextInput from '@components/TextInput'; import {propTypes as baseTextInputPropTypes, defaultProps as defaultBaseTextInputPropTypes} from '@components/TextInput/BaseTextInput/baseTextInputPropTypes'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import CalendarPicker from './CalendarPicker'; diff --git a/src/components/DeeplinkWrapper/DeeplinkRedirectLoadingIndicator.js b/src/components/DeeplinkWrapper/DeeplinkRedirectLoadingIndicator.js index 3c7366949ac1..622767b8a5f8 100644 --- a/src/components/DeeplinkWrapper/DeeplinkRedirectLoadingIndicator.js +++ b/src/components/DeeplinkWrapper/DeeplinkRedirectLoadingIndicator.js @@ -8,10 +8,10 @@ import * as Illustrations from '@components/Icon/Illustrations'; import Text from '@components/Text'; import TextLink from '@components/TextLink'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import Navigation from '@libs/Navigation/Navigation'; -import useTheme from '@styles/themes/useTheme'; -import useThemeStyles from '@styles/useThemeStyles'; import ONYXKEYS from '@src/ONYXKEYS'; import ROUTES from '@src/ROUTES'; diff --git a/src/components/DisplayNames/DisplayNamesTooltipItem.tsx b/src/components/DisplayNames/DisplayNamesTooltipItem.tsx index 82f9c5799b78..440457d22965 100644 --- a/src/components/DisplayNames/DisplayNamesTooltipItem.tsx +++ b/src/components/DisplayNames/DisplayNamesTooltipItem.tsx @@ -2,8 +2,8 @@ import React, {RefObject, useCallback} from 'react'; import {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 useThemeStyles from '@styles/useThemeStyles'; type DisplayNamesTooltipItemProps = { index?: number; diff --git a/src/components/DisplayNames/DisplayNamesWithTooltip.tsx b/src/components/DisplayNames/DisplayNamesWithTooltip.tsx index 8c8720c7c99f..43061ada9a94 100644 --- a/src/components/DisplayNames/DisplayNamesWithTooltip.tsx +++ b/src/components/DisplayNames/DisplayNamesWithTooltip.tsx @@ -2,7 +2,7 @@ import React, {Fragment, useCallback, useRef} from 'react'; import {Text as RNText, View} from 'react-native'; import Text from '@components/Text'; import Tooltip from '@components/Tooltip'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import DisplayNamesTooltipItem from './DisplayNamesTooltipItem'; import DisplayNamesProps from './types'; diff --git a/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx b/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx index 1854ebe2353d..761b0b66ee2c 100644 --- a/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx +++ b/src/components/DisplayNames/DisplayNamesWithoutTooltip.tsx @@ -1,7 +1,7 @@ import React from 'react'; import {StyleProp, TextStyle} from 'react-native'; import Text from '@components/Text'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; type DisplayNamesWithoutTooltipProps = { /** The full title of the DisplayNames component (not split up) */ diff --git a/src/components/DistanceEReceipt.js b/src/components/DistanceEReceipt.js index cbf25fd2753b..7d1d3c99f48d 100644 --- a/src/components/DistanceEReceipt.js +++ b/src/components/DistanceEReceipt.js @@ -5,13 +5,13 @@ import _ from 'underscore'; import EReceiptBackground from '@assets/images/eReceipt_background.svg'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as ReceiptUtils from '@libs/ReceiptUtils'; import * as ReportUtils from '@libs/ReportUtils'; import * as TransactionUtils from '@libs/TransactionUtils'; import tryResolveUrlFromApiRoot from '@libs/tryResolveUrlFromApiRoot'; -import useTheme from '@styles/themes/useTheme'; -import useThemeStyles from '@styles/useThemeStyles'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; import PendingMapView from './MapView/PendingMapView'; diff --git a/src/components/DistanceMapView/index.android.js b/src/components/DistanceMapView/index.android.js index 532d42ac0be5..fa40bd50673e 100644 --- a/src/components/DistanceMapView/index.android.js +++ b/src/components/DistanceMapView/index.android.js @@ -6,8 +6,8 @@ import * as Expensicons from '@components/Icon/Expensicons'; import MapView from '@components/MapView'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as distanceMapViewPropTypes from './distanceMapViewPropTypes'; function DistanceMapView(props) { diff --git a/src/components/DistanceRequest/DistanceRequestFooter.js b/src/components/DistanceRequest/DistanceRequestFooter.js index d374f90a1b6c..d2813c152a64 100644 --- a/src/components/DistanceRequest/DistanceRequestFooter.js +++ b/src/components/DistanceRequest/DistanceRequestFooter.js @@ -10,9 +10,9 @@ import DistanceMapView from '@components/DistanceMapView'; import * as Expensicons from '@components/Icon/Expensicons'; import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as TransactionUtils from '@libs/TransactionUtils'; -import useTheme from '@styles/themes/useTheme'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/components/DistanceRequest/DistanceRequestRenderItem.js b/src/components/DistanceRequest/DistanceRequestRenderItem.js index 1735e244a347..2aa2ac58f379 100644 --- a/src/components/DistanceRequest/DistanceRequestRenderItem.js +++ b/src/components/DistanceRequest/DistanceRequestRenderItem.js @@ -5,7 +5,7 @@ import _ from 'underscore'; import * as Expensicons from '@components/Icon/Expensicons'; import MenuItemWithTopDescription from '@components/MenuItemWithTopDescription'; import useLocalize from '@hooks/useLocalize'; -import useTheme from '@styles/themes/useTheme'; +import useTheme from '@hooks/useTheme'; const propTypes = { /** The waypoints for the distance request */ diff --git a/src/components/DistanceRequest/index.js b/src/components/DistanceRequest/index.js index be34a42ead5e..b2fd16cb7d97 100644 --- a/src/components/DistanceRequest/index.js +++ b/src/components/DistanceRequest/index.js @@ -14,12 +14,12 @@ import transactionPropTypes from '@components/transactionPropTypes'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; import usePrevious from '@hooks/usePrevious'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import * as IOUUtils from '@libs/IOUUtils'; import Navigation from '@libs/Navigation/Navigation'; import * as TransactionUtils from '@libs/TransactionUtils'; import reportPropTypes from '@pages/reportPropTypes'; -import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import * as MapboxToken from '@userActions/MapboxToken'; import * as Transaction from '@userActions/Transaction'; diff --git a/src/components/DotIndicatorMessage.tsx b/src/components/DotIndicatorMessage.tsx index 6a7d78768ed7..65afe8c7e4eb 100644 --- a/src/components/DotIndicatorMessage.tsx +++ b/src/components/DotIndicatorMessage.tsx @@ -1,11 +1,11 @@ /* eslint-disable react/no-array-index-key */ import React from 'react'; import {StyleProp, TextStyle, View, ViewStyle} from 'react-native'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import fileDownload from '@libs/fileDownload'; import * as Localize from '@libs/Localize'; -import useTheme from '@styles/themes/useTheme'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import Icon from './Icon'; import * as Expensicons from './Icon/Expensicons'; diff --git a/src/components/DragAndDrop/NoDropZone/index.tsx b/src/components/DragAndDrop/NoDropZone/index.tsx index 9f2c700b8918..4760a16fd20b 100644 --- a/src/components/DragAndDrop/NoDropZone/index.tsx +++ b/src/components/DragAndDrop/NoDropZone/index.tsx @@ -1,7 +1,7 @@ import React, {useRef} from 'react'; import {View} from 'react-native'; import useDragAndDrop from '@hooks/useDragAndDrop'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import type NoDropZoneProps from './types'; function NoDropZone({children}: NoDropZoneProps) { diff --git a/src/components/DragAndDrop/Provider/index.tsx b/src/components/DragAndDrop/Provider/index.tsx index 761c512497ac..a5da9cc45a36 100644 --- a/src/components/DragAndDrop/Provider/index.tsx +++ b/src/components/DragAndDrop/Provider/index.tsx @@ -3,7 +3,7 @@ import Str from 'expensify-common/lib/str'; import React, {useCallback, useEffect, useMemo, useRef} from 'react'; import {View} from 'react-native'; import useDragAndDrop from '@hooks/useDragAndDrop'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import type {DragAndDropContextParams, DragAndDropProviderProps, SetOnDropHandlerCallback} from './types'; const DragAndDropContext = React.createContext({}); diff --git a/src/components/DraggableList/index.native.tsx b/src/components/DraggableList/index.native.tsx index e7ff058234b7..f532b21720da 100644 --- a/src/components/DraggableList/index.native.tsx +++ b/src/components/DraggableList/index.native.tsx @@ -1,7 +1,7 @@ import React from 'react'; import DraggableFlatList from 'react-native-draggable-flatlist'; import {FlatList} from 'react-native-gesture-handler'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import type {DraggableListProps} from './types'; function DraggableList({renderClone, shouldUsePortal, ...viewProps}: DraggableListProps, ref: React.ForwardedRef>) { diff --git a/src/components/DraggableList/index.tsx b/src/components/DraggableList/index.tsx index 28128002c589..b92691075424 100644 --- a/src/components/DraggableList/index.tsx +++ b/src/components/DraggableList/index.tsx @@ -1,7 +1,7 @@ import React, {useCallback} from 'react'; import {DragDropContext, Draggable, Droppable, type OnDragEndResponder} from 'react-beautiful-dnd'; import {ScrollView} from 'react-native'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import type {DraggableListProps} from './types'; import useDraggableInPortal from './useDraggableInPortal'; diff --git a/src/components/EReceipt.js b/src/components/EReceipt.js index f5e5b7f2f6b3..12cea0df04ac 100644 --- a/src/components/EReceipt.js +++ b/src/components/EReceipt.js @@ -3,11 +3,11 @@ import React from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as CardUtils from '@libs/CardUtils'; import * as CurrencyUtils from '@libs/CurrencyUtils'; import * as ReportUtils from '@libs/ReportUtils'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/components/EReceiptThumbnail.js b/src/components/EReceiptThumbnail.js index 1f719862412b..7c782a0aa327 100644 --- a/src/components/EReceiptThumbnail.js +++ b/src/components/EReceiptThumbnail.js @@ -2,9 +2,9 @@ import PropTypes from 'prop-types'; import React, {useMemo, useState} from 'react'; import {View} from 'react-native'; import {withOnyx} from 'react-native-onyx'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as ReportUtils from '@libs/ReportUtils'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; diff --git a/src/components/EmojiPicker/CategoryShortcutBar.js b/src/components/EmojiPicker/CategoryShortcutBar.js index c0c9fb8ea161..facafea13a70 100644 --- a/src/components/EmojiPicker/CategoryShortcutBar.js +++ b/src/components/EmojiPicker/CategoryShortcutBar.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; import _ from 'underscore'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import CategoryShortcutButton from './CategoryShortcutButton'; const propTypes = { diff --git a/src/components/EmojiPicker/CategoryShortcutButton.js b/src/components/EmojiPicker/CategoryShortcutButton.js index 1fcbe7e863e4..a7dc0d7bf564 100644 --- a/src/components/EmojiPicker/CategoryShortcutButton.js +++ b/src/components/EmojiPicker/CategoryShortcutButton.js @@ -4,10 +4,10 @@ import Icon from '@components/Icon'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Tooltip from '@components/Tooltip'; import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; -import useTheme from '@styles/themes/useTheme'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; diff --git a/src/components/EmojiPicker/EmojiPicker.js b/src/components/EmojiPicker/EmojiPicker.js index 96d7ee88b816..6c05a4b6a5b5 100644 --- a/src/components/EmojiPicker/EmojiPicker.js +++ b/src/components/EmojiPicker/EmojiPicker.js @@ -4,10 +4,10 @@ import {Dimensions} from 'react-native'; import _ from 'underscore'; import PopoverWithMeasuredContent from '@components/PopoverWithMeasuredContent'; import withViewportOffsetTop from '@components/withViewportOffsetTop'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import calculateAnchorPosition from '@libs/calculateAnchorPosition'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import EmojiPickerMenu from './EmojiPickerMenu'; diff --git a/src/components/EmojiPicker/EmojiPickerButton.js b/src/components/EmojiPicker/EmojiPickerButton.js index 165646d4795d..869fe1edbfe5 100644 --- a/src/components/EmojiPicker/EmojiPickerButton.js +++ b/src/components/EmojiPicker/EmojiPickerButton.js @@ -5,9 +5,9 @@ import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Tooltip from '@components/Tooltip/PopoverAnchorTooltip'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; const propTypes = { diff --git a/src/components/EmojiPicker/EmojiPickerButtonDropdown.js b/src/components/EmojiPicker/EmojiPickerButtonDropdown.js index 8135fa38e992..43687ec4b7a8 100644 --- a/src/components/EmojiPicker/EmojiPickerButtonDropdown.js +++ b/src/components/EmojiPicker/EmojiPickerButtonDropdown.js @@ -5,11 +5,11 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Text from '@components/Text'; -import Tooltip from '@components/Tooltip'; +import Tooltip from '@components/Tooltip/PopoverAnchorTooltip'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import * as EmojiPickerAction from '@userActions/EmojiPickerAction'; import CONST from '@src/CONST'; @@ -47,7 +47,7 @@ function EmojiPickerButtonDropdown(props) { - debounce((comment, onExceededMaxCommentLength) => { - const newCommentLength = ReportUtils.getCommentLength(comment); - setCommentLength(newCommentLength); - onExceededMaxCommentLength(newCommentLength > CONST.MAX_COMMENT_LENGTH); - }, CONST.TIMING.COMMENT_LENGTH_DEBOUNCE_TIME), - [], - ); - - useEffect(() => { - updateCommentLength(props.comment, props.onExceededMaxCommentLength); - }, [props.comment, props.onExceededMaxCommentLength, updateCommentLength]); - if (commentLength <= CONST.MAX_COMMENT_LENGTH) { + if (!props.shouldShowError) { return null; } - return ( `${ONYXKEYS.COLLECTION.REPORT_DRAFT_COMMENT}${reportID}`, - initialValue: '', - }, -})(ExceededCommentLength); +export default ExceededCommentLength; diff --git a/src/components/ExpensifyWordmark.tsx b/src/components/ExpensifyWordmark.tsx index 49559d1cc6d5..b1ef7c093e29 100644 --- a/src/components/ExpensifyWordmark.tsx +++ b/src/components/ExpensifyWordmark.tsx @@ -5,9 +5,9 @@ import DevLogo from '@assets/images/expensify-logo--dev.svg'; import StagingLogo from '@assets/images/expensify-logo--staging.svg'; import ProductionLogo from '@assets/images/expensify-wordmark.svg'; import useEnvironment from '@hooks/useEnvironment'; -import useTheme from '@styles/themes/useTheme'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import variables from '@styles/variables'; import CONST from '@src/CONST'; import withWindowDimensions from './withWindowDimensions'; diff --git a/src/components/FeatureList.js b/src/components/FeatureList.js index 7b0c70372579..bf0852d179f1 100644 --- a/src/components/FeatureList.js +++ b/src/components/FeatureList.js @@ -3,7 +3,7 @@ import React from 'react'; import {View} from 'react-native'; import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import MenuItem from './MenuItem'; import menuItemPropTypes from './menuItemPropTypes'; import Text from './Text'; diff --git a/src/components/FixedFooter.tsx b/src/components/FixedFooter.tsx index 4b32e1b8ce81..7fd6811c1df6 100644 --- a/src/components/FixedFooter.tsx +++ b/src/components/FixedFooter.tsx @@ -1,6 +1,6 @@ import React, {ReactNode} from 'react'; import {StyleProp, View, ViewStyle} from 'react-native'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; type FixedFooterProps = { /** Children to wrap in FixedFooter. */ diff --git a/src/components/FocusModeNotification.js b/src/components/FocusModeNotification.js index ea6db244091a..e846c1f188e2 100644 --- a/src/components/FocusModeNotification.js +++ b/src/components/FocusModeNotification.js @@ -1,7 +1,7 @@ import React, {useEffect} from 'react'; import useEnvironment from '@hooks/useEnvironment'; import useLocalize from '@hooks/useLocalize'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as Link from '@userActions/Link'; import * as User from '@userActions/User'; import CONST from '@src/CONST'; @@ -10,9 +10,9 @@ import Text from './Text'; import TextLinkWithRef from './TextLink'; function FocusModeNotification() { + const styles = useThemeStyles(); const {environmentURL} = useEnvironment(); const {translate} = useLocalize(); - const styles = useThemeStyles(); useEffect(() => { User.updateChatPriorityMode(CONST.PRIORITY_MODE.GSD, true); }, []); diff --git a/src/components/Form.js b/src/components/Form.js index ad5fcf611e9b..7b6f587e7bd1 100644 --- a/src/components/Form.js +++ b/src/components/Form.js @@ -1,16 +1,16 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react'; +import React, {forwardRef, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {Keyboard, ScrollView, StyleSheet} from 'react-native'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; +import useThemeStyles from '@hooks/useThemeStyles'; import compose from '@libs/compose'; import * as ErrorUtils from '@libs/ErrorUtils'; import FormUtils from '@libs/FormUtils'; import * as ValidationUtils from '@libs/ValidationUtils'; import Visibility from '@libs/Visibility'; import stylePropTypes from '@styles/stylePropTypes'; -import useThemeStyles from '@styles/useThemeStyles'; import * as FormActions from '@userActions/FormActions'; import CONST from '@src/CONST'; import FormAlertWithSubmitButton from './FormAlertWithSubmitButton'; @@ -26,7 +26,7 @@ const propTypes = { formID: PropTypes.string.isRequired, /** Text to be displayed in the submit button */ - submitButtonText: PropTypes.string.isRequired, + submitButtonText: PropTypes.string, /** Controls the submit button's visibility */ isSubmitButtonVisible: PropTypes.bool, @@ -88,6 +88,9 @@ const propTypes = { /** Information about the network */ network: networkPropTypes.isRequired, + /** Style for the error message for submit button */ + errorMessageStyle: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + ...withLocalizePropTypes, }; @@ -104,11 +107,13 @@ const defaultProps = { shouldValidateOnBlur: true, footerContent: null, style: [], + errorMessageStyle: [], submitButtonStyles: [], validate: () => ({}), + submitButtonText: '', }; -function Form(props) { +const Form = forwardRef((props, forwardedRef) => { const styles = useThemeStyles(); const [errors, setErrors] = useState({}); const [inputValues, setInputValues] = useState(() => ({...props.draftValues})); @@ -245,6 +250,30 @@ function Form(props) { onSubmit(trimmedStringValues); }, [props.formState.isLoading, props.network.isOffline, props.enabledWhenOffline, inputValues, onValidate, onSubmit]); + /** + * Resets the form + */ + const resetForm = useCallback( + (optionalValue) => { + _.each(inputValues, (inputRef, inputID) => { + setInputValues((prevState) => { + const copyPrevState = _.clone(prevState); + + touchedInputs.current[inputID] = false; + copyPrevState[inputID] = optionalValue[inputID] || ''; + + return copyPrevState; + }); + }); + setErrors({}); + }, + [inputValues], + ); + + useImperativeHandle(forwardedRef, () => ({ + resetForm, + })); + /** * Loops over Form's children and automatically supplies Form props to them * @@ -464,7 +493,9 @@ function Form(props) { containerStyles={[styles.mh0, styles.mt5, styles.flex1, ...props.submitButtonStyles]} enabledWhenOffline={props.enabledWhenOffline} isSubmitActionDangerous={props.isSubmitActionDangerous} + useSmallerSubmitButtonSize={props.useSmallerSubmitButtonSize} disablePressOnEnter + errorMessageStyle={props.errorMessageStyle} /> )} @@ -474,6 +505,8 @@ function Form(props) { props.style, props.isSubmitButtonVisible, props.submitButtonText, + props.useSmallerSubmitButtonSize, + props.errorMessageStyle, props.formState.errorFields, props.formState.isLoading, props.footerContent, @@ -539,7 +572,7 @@ function Form(props) { } ); -} +}); Form.displayName = 'Form'; Form.propTypes = propTypes; diff --git a/src/components/Form/FormProvider.js b/src/components/Form/FormProvider.js index 63953d8303db..0d6dcb001091 100644 --- a/src/components/Form/FormProvider.js +++ b/src/components/Form/FormProvider.js @@ -1,6 +1,6 @@ import lodashGet from 'lodash/get'; import PropTypes from 'prop-types'; -import React, {createRef, useCallback, useMemo, useRef, useState} from 'react'; +import React, {createRef, forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState} from 'react'; import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import networkPropTypes from '@components/networkPropTypes'; @@ -109,250 +109,278 @@ function getInitialValueByType(valueType) { } } -function FormProvider({validate, formID, shouldValidateOnBlur, shouldValidateOnChange, children, formState, network, enabledWhenOffline, draftValues, onSubmit, ...rest}) { - const inputRefs = useRef({}); - const touchedInputs = useRef({}); - const [inputValues, setInputValues] = useState(() => ({...draftValues})); - const [errors, setErrors] = useState({}); - const hasServerError = useMemo(() => Boolean(formState) && !_.isEmpty(formState.errors), [formState]); +const FormProvider = forwardRef( + ({validate, formID, shouldValidateOnBlur, shouldValidateOnChange, children, formState, network, enabledWhenOffline, draftValues, onSubmit, ...rest}, forwardedRef) => { + const inputRefs = useRef({}); + const touchedInputs = useRef({}); + const [inputValues, setInputValues] = useState(() => ({...draftValues})); + const [errors, setErrors] = useState({}); + const hasServerError = useMemo(() => Boolean(formState) && !_.isEmpty(formState.errors), [formState]); - const onValidate = useCallback( - (values, shouldClearServerError = true) => { - const trimmedStringValues = ValidationUtils.prepareValues(values); + const onValidate = useCallback( + (values, shouldClearServerError = true) => { + const trimmedStringValues = ValidationUtils.prepareValues(values); - if (shouldClearServerError) { - FormActions.setErrors(formID, null); - } - FormActions.setErrorFields(formID, null); + if (shouldClearServerError) { + FormActions.setErrors(formID, null); + } + FormActions.setErrorFields(formID, null); - const validateErrors = validate(values) || {}; + const validateErrors = validate(values) || {}; - // Validate the input for html tags. It should supercede any other error - _.each(trimmedStringValues, (inputValue, inputID) => { - // If the input value is empty OR is non-string, we don't need to validate it for HTML tags - if (!inputValue || !_.isString(inputValue)) { - return; - } - const foundHtmlTagIndex = inputValue.search(CONST.VALIDATE_FOR_HTML_TAG_REGEX); - const leadingSpaceIndex = inputValue.search(CONST.VALIDATE_FOR_LEADINGSPACES_HTML_TAG_REGEX); + // Validate the input for html tags. It should supercede any other error + _.each(trimmedStringValues, (inputValue, inputID) => { + // If the input value is empty OR is non-string, we don't need to validate it for HTML tags + if (!inputValue || !_.isString(inputValue)) { + return; + } + const foundHtmlTagIndex = inputValue.search(CONST.VALIDATE_FOR_HTML_TAG_REGEX); + const leadingSpaceIndex = inputValue.search(CONST.VALIDATE_FOR_LEADINGSPACES_HTML_TAG_REGEX); - // Return early if there are no HTML characters - if (leadingSpaceIndex === -1 && foundHtmlTagIndex === -1) { - return; - } + // Return early if there are no HTML characters + if (leadingSpaceIndex === -1 && foundHtmlTagIndex === -1) { + return; + } - const matchedHtmlTags = inputValue.match(CONST.VALIDATE_FOR_HTML_TAG_REGEX); - let isMatch = _.some(CONST.WHITELISTED_TAGS, (r) => r.test(inputValue)); - // Check for any matches that the original regex (foundHtmlTagIndex) matched - if (matchedHtmlTags) { - // Check if any matched inputs does not match in WHITELISTED_TAGS list and return early if needed. - for (let i = 0; i < matchedHtmlTags.length; i++) { - const htmlTag = matchedHtmlTags[i]; - isMatch = _.some(CONST.WHITELISTED_TAGS, (r) => r.test(htmlTag)); - if (!isMatch) { - break; + const matchedHtmlTags = inputValue.match(CONST.VALIDATE_FOR_HTML_TAG_REGEX); + let isMatch = _.some(CONST.WHITELISTED_TAGS, (r) => r.test(inputValue)); + // Check for any matches that the original regex (foundHtmlTagIndex) matched + if (matchedHtmlTags) { + // Check if any matched inputs does not match in WHITELISTED_TAGS list and return early if needed. + for (let i = 0; i < matchedHtmlTags.length; i++) { + const htmlTag = matchedHtmlTags[i]; + isMatch = _.some(CONST.WHITELISTED_TAGS, (r) => r.test(htmlTag)); + if (!isMatch) { + break; + } } } + // Add a validation error here because it is a string value that contains HTML characters + validateErrors[inputID] = 'common.error.invalidCharacter'; + }); + + if (!_.isObject(validateErrors)) { + throw new Error('Validate callback must return an empty object or an object with shape {inputID: error}'); } - // Add a validation error here because it is a string value that contains HTML characters - validateErrors[inputID] = 'common.error.invalidCharacter'; - }); - if (!_.isObject(validateErrors)) { - throw new Error('Validate callback must return an empty object or an object with shape {inputID: error}'); - } + const touchedInputErrors = _.pick(validateErrors, (inputValue, inputID) => Boolean(touchedInputs.current[inputID])); - const touchedInputErrors = _.pick(validateErrors, (inputValue, inputID) => Boolean(touchedInputs.current[inputID])); + if (!_.isEqual(errors, touchedInputErrors)) { + setErrors(touchedInputErrors); + } - if (!_.isEqual(errors, touchedInputErrors)) { - setErrors(touchedInputErrors); + return touchedInputErrors; + }, + [errors, formID, validate], + ); + + /** + * @param {String} inputID - The inputID of the input being touched + */ + const setTouchedInput = useCallback( + (inputID) => { + touchedInputs.current[inputID] = true; + }, + [touchedInputs], + ); + + const submit = useCallback(() => { + // Return early if the form is already submitting to avoid duplicate submission + if (formState.isLoading) { + return; } - return touchedInputErrors; - }, - [errors, formID, validate], - ); - - /** - * @param {String} inputID - The inputID of the input being touched - */ - const setTouchedInput = useCallback( - (inputID) => { - touchedInputs.current[inputID] = true; - }, - [touchedInputs], - ); - - const submit = useCallback(() => { - // Return early if the form is already submitting to avoid duplicate submission - if (formState.isLoading) { - return; - } - - // Prepare values before submitting - const trimmedStringValues = ValidationUtils.prepareValues(inputValues); - - // Touches all form inputs so we can validate the entire form - _.each(inputRefs.current, (inputRef, inputID) => (touchedInputs.current[inputID] = true)); - - // Validate form and return early if any errors are found - if (!_.isEmpty(onValidate(trimmedStringValues))) { - return; - } - - // Do not submit form if network is offline and the form is not enabled when offline - if (network.isOffline && !enabledWhenOffline) { - return; - } - - onSubmit(trimmedStringValues); - }, [enabledWhenOffline, formState.isLoading, inputValues, network.isOffline, onSubmit, onValidate]); - - const registerInput = useCallback( - (inputID, propsToParse = {}) => { - const newRef = inputRefs.current[inputID] || propsToParse.ref || createRef(); - if (inputRefs.current[inputID] !== newRef) { - inputRefs.current[inputID] = newRef; + // Prepare values before submitting + const trimmedStringValues = ValidationUtils.prepareValues(inputValues); + + // Touches all form inputs so we can validate the entire form + _.each(inputRefs.current, (inputRef, inputID) => (touchedInputs.current[inputID] = true)); + + // Validate form and return early if any errors are found + if (!_.isEmpty(onValidate(trimmedStringValues))) { + return; } - if (!_.isUndefined(propsToParse.value)) { - inputValues[inputID] = propsToParse.value; - } else if (propsToParse.shouldSaveDraft && !_.isUndefined(draftValues[inputID]) && _.isUndefined(inputValues[inputID])) { - inputValues[inputID] = draftValues[inputID]; - } else if (propsToParse.shouldUseDefaultValue && _.isUndefined(inputValues[inputID])) { - // We force the form to set the input value from the defaultValue props if there is a saved valid value - inputValues[inputID] = propsToParse.defaultValue; - } else if (_.isUndefined(inputValues[inputID])) { - // We want to initialize the input value if it's undefined - inputValues[inputID] = _.isUndefined(propsToParse.defaultValue) ? getInitialValueByType(propsToParse.valueType) : propsToParse.defaultValue; + // Do not submit form if network is offline and the form is not enabled when offline + if (network.isOffline && !enabledWhenOffline) { + return; } - const errorFields = lodashGet(formState, 'errorFields', {}); - const fieldErrorMessage = - _.chain(errorFields[inputID]) - .keys() - .sortBy() - .reverse() - .map((key) => errorFields[inputID][key]) - .first() - .value() || ''; - - return { - ...propsToParse, - ref: - typeof propsToParse.ref === 'function' - ? (node) => { - propsToParse.ref(node); - newRef.current = node; - } - : newRef, - inputID, - key: propsToParse.key || inputID, - errorText: errors[inputID] || fieldErrorMessage, - value: inputValues[inputID], - // As the text input is controlled, we never set the defaultValue prop - // as this is already happening by the value prop. - defaultValue: undefined, - onTouched: (event) => { - if (!propsToParse.shouldSetTouchedOnBlurOnly) { - setTimeout(() => { - setTouchedInput(inputID); - }, VALIDATE_DELAY); - } - if (_.isFunction(propsToParse.onTouched)) { - propsToParse.onTouched(event); - } - }, - onPress: (event) => { - if (!propsToParse.shouldSetTouchedOnBlurOnly) { - setTimeout(() => { - setTouchedInput(inputID); - }, VALIDATE_DELAY); - } - if (_.isFunction(propsToParse.onPress)) { - propsToParse.onPress(event); - } - }, - onPressOut: (event) => { - // To prevent validating just pressed inputs, we need to set the touched input right after - // onValidate and to do so, we need to delays setTouchedInput of the same amount of time - // as the onValidate is delayed - if (!propsToParse.shouldSetTouchedOnBlurOnly) { - setTimeout(() => { - setTouchedInput(inputID); - }, VALIDATE_DELAY); - } - if (_.isFunction(propsToParse.onPressIn)) { - propsToParse.onPressIn(event); - } - }, - onBlur: (event) => { - // Only run validation when user proactively blurs the input. - if (Visibility.isVisible() && Visibility.hasFocus()) { - const relatedTargetId = lodashGet(event, 'nativeEvent.relatedTarget.id'); - // We delay the validation in order to prevent Checkbox loss of focus when - // the user is focusing a TextInput and proceeds to toggle a CheckBox in - // web and mobile web platforms. - - setTimeout(() => { - if (relatedTargetId && _.includes([CONST.OVERLAY.BOTTOM_BUTTON_NATIVE_ID, CONST.OVERLAY.TOP_BUTTON_NATIVE_ID, CONST.BACK_BUTTON_NATIVE_ID], relatedTargetId)) { - return; - } - setTouchedInput(inputID); - if (shouldValidateOnBlur) { - onValidate(inputValues, !hasServerError); - } - }, VALIDATE_DELAY); - } + onSubmit(trimmedStringValues); + }, [enabledWhenOffline, formState.isLoading, inputValues, network.isOffline, onSubmit, onValidate]); - if (_.isFunction(propsToParse.onBlur)) { - propsToParse.onBlur(event); - } - }, - onInputChange: (value, key) => { - const inputKey = key || inputID; + /** + * Resets the form + */ + const resetForm = useCallback( + (optionalValue) => { + _.each(inputValues, (inputRef, inputID) => { setInputValues((prevState) => { - const newState = { - ...prevState, - [inputKey]: value, - }; + const copyPrevState = _.clone(prevState); - if (shouldValidateOnChange) { - onValidate(newState); - } - return newState; + touchedInputs.current[inputID] = false; + copyPrevState[inputID] = optionalValue[inputID] || ''; + + return copyPrevState; }); + }); + setErrors({}); + }, + [inputValues], + ); + useImperativeHandle(forwardedRef, () => ({ + resetForm, + })); + + const registerInput = useCallback( + (inputID, propsToParse = {}) => { + const newRef = inputRefs.current[inputID] || propsToParse.ref || createRef(); + if (inputRefs.current[inputID] !== newRef) { + inputRefs.current[inputID] = newRef; + } - if (propsToParse.shouldSaveDraft) { - FormActions.setDraftValues(formID, {[inputKey]: value}); - } + if (!_.isUndefined(propsToParse.value)) { + inputValues[inputID] = propsToParse.value; + } else if (propsToParse.shouldSaveDraft && !_.isUndefined(draftValues[inputID]) && _.isUndefined(inputValues[inputID])) { + inputValues[inputID] = draftValues[inputID]; + } else if (propsToParse.shouldUseDefaultValue && _.isUndefined(inputValues[inputID])) { + // We force the form to set the input value from the defaultValue props if there is a saved valid value + inputValues[inputID] = propsToParse.defaultValue; + } else if (_.isUndefined(inputValues[inputID])) { + // We want to initialize the input value if it's undefined + inputValues[inputID] = _.isUndefined(propsToParse.defaultValue) ? getInitialValueByType(propsToParse.valueType) : propsToParse.defaultValue; + } - if (_.isFunction(propsToParse.onValueChange)) { - propsToParse.onValueChange(value, inputKey); - } - }, - }; - }, - [draftValues, formID, errors, formState, hasServerError, inputValues, onValidate, setTouchedInput, shouldValidateOnBlur, shouldValidateOnChange], - ); - const value = useMemo(() => ({registerInput}), [registerInput]); - - return ( - - {/* eslint-disable react/jsx-props-no-spreading */} - - {_.isFunction(children) ? children({inputValues}) : children} - - - ); -} + const errorFields = lodashGet(formState, 'errorFields', {}); + const fieldErrorMessage = + _.chain(errorFields[inputID]) + .keys() + .sortBy() + .reverse() + .map((key) => errorFields[inputID][key]) + .first() + .value() || ''; + + return { + ...propsToParse, + ref: + typeof propsToParse.ref === 'function' + ? (node) => { + propsToParse.ref(node); + newRef.current = node; + } + : newRef, + inputID, + key: propsToParse.key || inputID, + errorText: errors[inputID] || fieldErrorMessage, + value: inputValues[inputID], + // As the text input is controlled, we never set the defaultValue prop + // as this is already happening by the value prop. + defaultValue: undefined, + onTouched: (event) => { + if (!propsToParse.shouldSetTouchedOnBlurOnly) { + setTimeout(() => { + setTouchedInput(inputID); + }, VALIDATE_DELAY); + } + if (_.isFunction(propsToParse.onTouched)) { + propsToParse.onTouched(event); + } + }, + onPress: (event) => { + if (!propsToParse.shouldSetTouchedOnBlurOnly) { + setTimeout(() => { + setTouchedInput(inputID); + }, VALIDATE_DELAY); + } + if (_.isFunction(propsToParse.onPress)) { + propsToParse.onPress(event); + } + }, + onPressOut: (event) => { + // To prevent validating just pressed inputs, we need to set the touched input right after + // onValidate and to do so, we need to delays setTouchedInput of the same amount of time + // as the onValidate is delayed + if (!propsToParse.shouldSetTouchedOnBlurOnly) { + setTimeout(() => { + setTouchedInput(inputID); + }, VALIDATE_DELAY); + } + if (_.isFunction(propsToParse.onPressIn)) { + propsToParse.onPressIn(event); + } + }, + onBlur: (event) => { + // Only run validation when user proactively blurs the input. + if (Visibility.isVisible() && Visibility.hasFocus()) { + const relatedTargetId = lodashGet(event, 'nativeEvent.relatedTarget.id'); + // We delay the validation in order to prevent Checkbox loss of focus when + // the user is focusing a TextInput and proceeds to toggle a CheckBox in + // web and mobile web platforms. + + setTimeout(() => { + if ( + relatedTargetId && + _.includes([CONST.OVERLAY.BOTTOM_BUTTON_NATIVE_ID, CONST.OVERLAY.TOP_BUTTON_NATIVE_ID, CONST.BACK_BUTTON_NATIVE_ID], relatedTargetId) + ) { + return; + } + setTouchedInput(inputID); + if (shouldValidateOnBlur) { + onValidate(inputValues, !hasServerError); + } + }, VALIDATE_DELAY); + } + + if (_.isFunction(propsToParse.onBlur)) { + propsToParse.onBlur(event); + } + }, + onInputChange: (value, key) => { + const inputKey = key || inputID; + setInputValues((prevState) => { + const newState = { + ...prevState, + [inputKey]: value, + }; + + if (shouldValidateOnChange) { + onValidate(newState); + } + return newState; + }); + + if (propsToParse.shouldSaveDraft) { + FormActions.setDraftValues(formID, {[inputKey]: value}); + } + + if (_.isFunction(propsToParse.onValueChange)) { + propsToParse.onValueChange(value, inputKey); + } + }, + }; + }, + [draftValues, formID, errors, formState, hasServerError, inputValues, onValidate, setTouchedInput, shouldValidateOnBlur, shouldValidateOnChange], + ); + const value = useMemo(() => ({registerInput}), [registerInput]); + + return ( + + {/* eslint-disable react/jsx-props-no-spreading */} + + {_.isFunction(children) ? children({inputValues}) : children} + + + ); + }, +); FormProvider.displayName = 'Form'; FormProvider.propTypes = propTypes; diff --git a/src/components/Form/FormWrapper.js b/src/components/Form/FormWrapper.js index 638b6e5f8d19..da34262a8af8 100644 --- a/src/components/Form/FormWrapper.js +++ b/src/components/Form/FormWrapper.js @@ -8,9 +8,9 @@ import FormSubmit from '@components/FormSubmit'; import refPropTypes from '@components/refPropTypes'; import SafeAreaConsumer from '@components/SafeAreaConsumer'; import ScrollViewWithContext from '@components/ScrollViewWithContext'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as ErrorUtils from '@libs/ErrorUtils'; import stylePropTypes from '@styles/stylePropTypes'; -import useThemeStyles from '@styles/useThemeStyles'; import errorsPropType from './errorsPropType'; const propTypes = { diff --git a/src/components/FormAlertWithSubmitButton.js b/src/components/FormAlertWithSubmitButton.js index b16a4d2a08ee..86e88c27b388 100644 --- a/src/components/FormAlertWithSubmitButton.js +++ b/src/components/FormAlertWithSubmitButton.js @@ -2,7 +2,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import {View} from 'react-native'; import _ from 'underscore'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import Button from './Button'; import FormAlertWrapper from './FormAlertWrapper'; @@ -50,6 +50,12 @@ const propTypes = { /** Styles for the button */ // eslint-disable-next-line react/forbid-prop-types buttonStyles: PropTypes.arrayOf(PropTypes.object), + + /** Whether to use a smaller submit button size */ + useSmallerSubmitButtonSize: PropTypes.bool, + + /** Style for the error message for submit button */ + errorMessageStyle: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), }; const defaultProps = { @@ -62,8 +68,10 @@ const defaultProps = { enabledWhenOffline: false, disablePressOnEnter: false, isSubmitActionDangerous: false, + useSmallerSubmitButtonSize: false, footerContent: null, buttonStyles: [], + errorMessageStyle: [], }; function FormAlertWithSubmitButton(props) { @@ -77,6 +85,7 @@ function FormAlertWithSubmitButton(props) { isMessageHtml={props.isMessageHtml} message={props.message} onFixTheErrorsLinkPressed={props.onFixTheErrorsLinkPressed} + errorMessageStyle={props.errorMessageStyle} > {(isOffline) => ( @@ -87,6 +96,7 @@ function FormAlertWithSubmitButton(props) { text={props.buttonText} style={buttonStyles} danger={props.isSubmitActionDangerous} + medium={props.useSmallerSubmitButtonSize} /> ) : (