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 06695c7c02b9..0849b1cf59f9 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 1001041116 - versionName "1.4.11-16" + versionCode 1001041202 + versionName "1.4.12-2" } flavorDimensions "default" diff --git a/desktop/package-lock.json b/desktop/package-lock.json index 0ff280c4b9c6..39b186beb022 100644 --- a/desktop/package-lock.json +++ b/desktop/package-lock.json @@ -9,8 +9,8 @@ "dependencies": { "electron-context-menu": "^2.3.0", "electron-log": "^4.4.7", - "electron-serve": "^1.0.0", - "electron-updater": "^6.1.4", + "electron-serve": "^1.2.0", + "electron-updater": "^6.1.6", "node-machine-id": "^1.1.12" } }, @@ -50,9 +50,9 @@ } }, "node_modules/builder-util-runtime": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz", - "integrity": "sha512-2rLv/uQD2x+dJ0J3xtsmI12AlRyk7p45TEbE/6o/fbb633e/S3pPgm+ct+JHsoY7r39dKHnGEFk/AASRFdnXmA==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.2.tgz", + "integrity": "sha512-Or2/ycVYRGQ876hKMfiz2Ghgzh3WllgPW75jqt1Ta2a5wprpnziFrHpQ9eUq6/ScsVXMnG4PmQqlMsE9NFg8DQ==", "dependencies": { "debug": "^4.3.4", "sax": "^1.2.4" @@ -145,16 +145,22 @@ "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.4", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.4.tgz", - "integrity": "sha512-yYAJc6RQjjV4WtInZVn+ZcLyXRhbVXoomKEfUUwDqIk5s2wxzLhWaor7lrNgxODyODhipjg4SVPMhJHi5EnsCA==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.6.tgz", + "integrity": "sha512-G2bO72i7kv+bVdBAjq6lQn8zkZ3wMRRjxBD4TGBjB77UiuMDUBeP45YAs4y08udPzttGW2qzpnbOUJY5RYWZfw==", "dependencies": { - "builder-util-runtime": "9.2.1", + "builder-util-runtime": "9.2.2", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", @@ -461,9 +467,9 @@ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==" }, "builder-util-runtime": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.1.tgz", - "integrity": "sha512-2rLv/uQD2x+dJ0J3xtsmI12AlRyk7p45TEbE/6o/fbb633e/S3pPgm+ct+JHsoY7r39dKHnGEFk/AASRFdnXmA==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-9.2.2.tgz", + "integrity": "sha512-Or2/ycVYRGQ876hKMfiz2Ghgzh3WllgPW75jqt1Ta2a5wprpnziFrHpQ9eUq6/ScsVXMnG4PmQqlMsE9NFg8DQ==", "requires": { "debug": "^4.3.4", "sax": "^1.2.4" @@ -530,16 +536,16 @@ "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.4", - "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.4.tgz", - "integrity": "sha512-yYAJc6RQjjV4WtInZVn+ZcLyXRhbVXoomKEfUUwDqIk5s2wxzLhWaor7lrNgxODyODhipjg4SVPMhJHi5EnsCA==", + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-6.1.6.tgz", + "integrity": "sha512-G2bO72i7kv+bVdBAjq6lQn8zkZ3wMRRjxBD4TGBjB77UiuMDUBeP45YAs4y08udPzttGW2qzpnbOUJY5RYWZfw==", "requires": { - "builder-util-runtime": "9.2.1", + "builder-util-runtime": "9.2.2", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0", "lazy-val": "^1.0.5", diff --git a/desktop/package.json b/desktop/package.json index bf49d93f1a7b..7689c18f0dbd 100644 --- a/desktop/package.json +++ b/desktop/package.json @@ -6,8 +6,8 @@ "dependencies": { "electron-context-menu": "^2.3.0", "electron-log": "^4.4.7", - "electron-serve": "^1.0.0", - "electron-updater": "^6.1.4", + "electron-serve": "^1.2.0", + "electron-updater": "^6.1.6", "node-machine-id": "^1.1.12" }, "author": "Expensify, Inc.", 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 d7b29e9b9487..1e827cab6935 100644 --- a/ios/NewExpensify/Info.plist +++ b/ios/NewExpensify/Info.plist @@ -19,7 +19,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.4.11 + 1.4.12 CFBundleSignature ???? CFBundleURLTypes @@ -40,7 +40,7 @@ CFBundleVersion - 1.4.11.16 + 1.4.12.2 ITSAppUsesNonExemptEncryption LSApplicationQueriesSchemes diff --git a/ios/NewExpensifyTests/Info.plist b/ios/NewExpensifyTests/Info.plist index eafa6e487154..9e489e59474f 100644 --- a/ios/NewExpensifyTests/Info.plist +++ b/ios/NewExpensifyTests/Info.plist @@ -15,10 +15,10 @@ CFBundlePackageType BNDL CFBundleShortVersionString - 1.4.11 + 1.4.12 CFBundleSignature ???? CFBundleVersion - 1.4.11.16 + 1.4.12.2 diff --git a/package-lock.json b/package-lock.json index f11bfa005834..c6c12e491cde 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "new.expensify", - "version": "1.4.11-16", + "version": "1.4.12-2", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "new.expensify", - "version": "1.4.11-16", + "version": "1.4.12-2", "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 b18c616f6bb4..dba9988e34e8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "new.expensify", - "version": "1.4.11-16", + "version": "1.4.12-2", "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..12fc6a9426e1 100644 --- a/src/App.js +++ b/src/App.js @@ -16,6 +16,9 @@ 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()) { diff --git a/src/CONST.ts b/src/CONST.ts index d4208d51a78c..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: { @@ -1099,11 +1101,6 @@ const CONST = { USER_CANCELLED: 'User canceled flow.', USER_TAPPED_BACK: 'User exited by clicking the back button.', USER_EXITED: 'User exited by manual action.', - USER_CAMERA_DENINED: 'Onfido.OnfidoFlowError', - USER_CAMERA_PERMISSION: 'Encountered an error: cameraPermission', - // eslint-disable-next-line max-len - USER_CAMERA_CONSENT_DENIED: - 'Unexpected result Intent. It might be a result of incorrect integration, make sure you only pass Onfido intent to handleActivityResult. It might be due to unpredictable crash or error. Please report the problem to android-sdk@onfido.com. Intent: null \n resultCode: 0', }, }, @@ -2731,17 +2728,125 @@ const CONST = { EXPENSIFY_LOGO_SIZE_RATIO: 0.22, EXPENSIFY_LOGO_MARGIN_RATIO: 0.03, }, + /** + * Acceptable values for the `accessibilityRole` prop on react native components. + * + * **IMPORTANT:** Do not use with the `role` prop as it can cause errors. + * + * @deprecated ACCESSIBILITY_ROLE is deprecated. Please use CONST.ROLE instead. + */ ACCESSIBILITY_ROLE: { + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ BUTTON: 'button', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ LINK: 'link', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ MENUITEM: 'menuitem', - TEXT: 'presentation', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ + TEXT: 'text', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ RADIO: 'radio', - IMAGEBUTTON: 'img button', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ + IMAGEBUTTON: 'imagebutton', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ CHECKBOX: 'checkbox', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ SWITCH: 'switch', - ADJUSTABLE: 'slider', - IMAGE: 'img', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ + ADJUSTABLE: 'adjustable', + + /** + * @deprecated Please stop using the accessibilityRole prop and use the role prop instead. + */ + IMAGE: 'image', + }, + /** + * Acceptable values for the `role` attribute on react native components. + * + * **IMPORTANT:** Not for use with the `accessibilityRole` prop, as it accepts different values, and new components + * should use the `role` prop instead. + */ + ROLE: { + /** Use for elements with important, time-sensitive information. */ + ALERT: 'alert', + /** Use for elements that act as buttons. */ + BUTTON: 'button', + /** Use for elements representing checkboxes. */ + CHECKBOX: 'checkbox', + /** Use for elements that allow a choice from multiple options. */ + COMBOBOX: 'combobox', + /** Use with scrollable lists to represent a grid layout. */ + GRID: 'grid', + /** Use for section headers or titles. */ + HEADING: 'heading', + /** Use for image elements. */ + IMG: 'img', + /** Use for elements that navigate to other pages or content. */ + LINK: 'link', + /** Use to identify a list of items. */ + LIST: 'list', + /** Use for a list of choices or options. */ + MENU: 'menu', + /** Use for a container of multiple menus. */ + MENUBAR: 'menubar', + /** Use for items within a menu. */ + MENUITEM: 'menuitem', + /** Use when no specific role is needed. */ + NONE: 'none', + /** Use for elements that don't require a specific role. */ + PRESENTATION: 'presentation', + /** Use for elements showing progress of a task. */ + PROGRESSBAR: 'progressbar', + /** Use for radio buttons. */ + RADIO: 'radio', + /** Use for groups of radio buttons. */ + RADIOGROUP: 'radiogroup', + /** Use for scrollbar elements. */ + SCROLLBAR: 'scrollbar', + /** Use for text fields that are used for searching. */ + SEARCHBOX: 'searchbox', + /** Use for adjustable elements like sliders. */ + SLIDER: 'slider', + /** Use for a button that opens a list of choices. */ + SPINBUTTON: 'spinbutton', + /** Use for elements providing a summary of app conditions. */ + SUMMARY: 'summary', + /** Use for on/off switch elements. */ + SWITCH: 'switch', + /** Use for tab elements in a tab list. */ + TAB: 'tab', + /** Use for a list of tabs. */ + TABLIST: 'tablist', + /** Use for timer elements. */ + TIMER: 'timer', + /** Use for toolbars containing action buttons or components. */ + TOOLBAR: 'toolbar', }, TRANSLATION_KEYS: { ATTACHMENT: 'common.attachment', @@ -2799,6 +2904,10 @@ const CONST = { NAVIGATE: 'NAVIGATE', }, }, + TIME_PERIOD: { + AM: 'AM', + PM: 'PM', + }, INDENTS: ' ', PARENT_CHILD_SEPARATOR: ': ', CATEGORY_LIST_THRESHOLD: 8, @@ -2808,7 +2917,7 @@ const CONST = { SBE: 'SbeDemoSetup', MONEY2020: 'Money2020DemoSetup', }, - + COLON: ':', MAPBOX: { PADDING: 50, DEFAULT_ZOOM: 10, @@ -2854,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/Expensify.js b/src/Expensify.js index aece93c0ff4d..756df5b79b88 100644 --- a/src/Expensify.js +++ b/src/Expensify.js @@ -112,6 +112,7 @@ function Expensify(props) { }, [props.isCheckingPublicRoom]); const isAuthenticated = useMemo(() => Boolean(lodashGet(props.session, 'authToken', null)), [props.session]); + const autoAuthState = useMemo(() => lodashGet(props.session, 'autoAuthState', ''), [props.session]); const contextValue = useMemo( () => ({ @@ -207,7 +208,10 @@ function Expensify(props) { } return ( - + {shouldInit && ( <> diff --git a/src/ONYXKEYS.ts b/src/ONYXKEYS.ts index a268c008cee8..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', @@ -383,7 +383,7 @@ type OnyxValues = { [ONYXKEYS.COUNTRY]: string; [ONYXKEYS.USER]: OnyxTypes.User; [ONYXKEYS.USER_LOCATION]: OnyxTypes.UserLocation; - [ONYXKEYS.LOGIN_LIST]: Record; + [ONYXKEYS.LOGIN_LIST]: OnyxTypes.LoginList; [ONYXKEYS.SESSION]: OnyxTypes.Session; [ONYXKEYS.BETAS]: OnyxTypes.Beta[]; [ONYXKEYS.NVP_PRIORITY_MODE]: ValueOf; @@ -403,8 +403,8 @@ type OnyxValues = { [ONYXKEYS.WALLET_ONFIDO]: OnyxTypes.WalletOnfido; [ONYXKEYS.WALLET_ADDITIONAL_DETAILS]: OnyxTypes.WalletAdditionalDetails; [ONYXKEYS.WALLET_TERMS]: OnyxTypes.WalletTerms; - [ONYXKEYS.BANK_ACCOUNT_LIST]: Record; - [ONYXKEYS.FUND_LIST]: Record; + [ONYXKEYS.BANK_ACCOUNT_LIST]: OnyxTypes.BankAccountList; + [ONYXKEYS.FUND_LIST]: OnyxTypes.FundList; [ONYXKEYS.CARD_LIST]: Record; [ONYXKEYS.WALLET_STATEMENT]: OnyxTypes.WalletStatement; [ONYXKEYS.PERSONAL_BANK_ACCOUNT]: OnyxTypes.PersonalBankAccount; @@ -440,7 +440,7 @@ type OnyxValues = { [ONYXKEYS.COLLECTION.POLICY_DRAFTS]: OnyxTypes.Policy; [ONYXKEYS.COLLECTION.POLICY_CATEGORIES]: OnyxTypes.PolicyCategory; [ONYXKEYS.COLLECTION.POLICY_TAGS]: OnyxTypes.PolicyTags; - [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMember; + [ONYXKEYS.COLLECTION.POLICY_MEMBERS]: OnyxTypes.PolicyMembers; [ONYXKEYS.COLLECTION.POLICY_MEMBERS_DRAFTS]: OnyxTypes.PolicyMember; [ONYXKEYS.COLLECTION.POLICY_RECENTLY_USED_CATEGORIES]: OnyxTypes.RecentlyUsedCategories; [ONYXKEYS.COLLECTION.DEPRECATED_POLICY_MEMBER_LIST]: OnyxTypes.PolicyMembers; @@ -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 53763d6d7cd1..ca1fe9f0e81a 100644 --- a/src/ROUTES.ts +++ b/src/ROUTES.ts @@ -34,7 +34,7 @@ const ROUTES = { VALIDATE_LOGIN: 'v/:accountID/:validateCode', GET_ASSISTANCE: { route: 'get-assistance/:taskID', - getRoute: (taskID: string) => `get-assistance/${taskID}` as const, + getRoute: (taskID: string, backTo: string) => getUrlWithBackToParam(`get-assistance/${taskID}`, backTo), }, UNLINK_LOGIN: 'u/:accountID/:validateCode', APPLE_SIGN_IN: 'sign-in-with-apple', @@ -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/AddPlaidBankAccount.js b/src/components/AddPlaidBankAccount.js index 68d529c4a78d..6e3d0e2f0d68 100644 --- a/src/components/AddPlaidBankAccount.js +++ b/src/components/AddPlaidBankAccount.js @@ -6,11 +6,11 @@ import {withOnyx} from 'react-native-onyx'; import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; import useNetwork from '@hooks/useNetwork'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import KeyboardShortcut from '@libs/KeyboardShortcut'; import Log from '@libs/Log'; import {plaidDataPropTypes} from '@pages/ReimbursementAccount/plaidDataPropTypes'; -import useTheme from '@styles/themes/useTheme'; -import useThemeStyles from '@styles/useThemeStyles'; import * as App from '@userActions/App'; import * as BankAccounts from '@userActions/BankAccounts'; import CONST from '@src/CONST'; diff --git a/src/components/AddressForm.js b/src/components/AddressForm.js index 1621328d388f..68d451e5c7c8 100644 --- a/src/components/AddressForm.js +++ b/src/components/AddressForm.js @@ -5,9 +5,9 @@ import React, {useCallback} from 'react'; import {View} from 'react-native'; import _ from 'underscore'; import useLocalize from '@hooks/useLocalize'; +import useThemeStyles from '@hooks/useThemeStyles'; import Navigation from '@libs/Navigation/Navigation'; import * as ValidationUtils from '@libs/ValidationUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import AddressSearch from './AddressSearch'; import CountrySelector from './CountrySelector'; diff --git a/src/components/AddressSearch/CurrentLocationButton.js b/src/components/AddressSearch/CurrentLocationButton.js index 90d2c15733f1..06541565f567 100644 --- a/src/components/AddressSearch/CurrentLocationButton.js +++ b/src/components/AddressSearch/CurrentLocationButton.js @@ -5,10 +5,10 @@ import Icon from '@components/Icon'; import * as Expensicons from '@components/Icon/Expensicons'; import PressableWithFeedback from '@components/Pressable/PressableWithFeedback'; import useLocalize from '@hooks/useLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import getButtonState from '@libs/getButtonState'; -import colors from '@styles/colors'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; +import colors from '@styles/theme/colors'; const propTypes = { /** Callback that runs when location button is clicked */ diff --git a/src/components/AddressSearch/index.js b/src/components/AddressSearch/index.js index 2fed1d153947..3c764b36f3eb 100644 --- a/src/components/AddressSearch/index.js +++ b/src/components/AddressSearch/index.js @@ -10,13 +10,13 @@ import networkPropTypes from '@components/networkPropTypes'; import {withNetwork} from '@components/OnyxProvider'; import TextInput from '@components/TextInput'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useTheme from '@hooks/useTheme'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as ApiUtils from '@libs/ApiUtils'; import compose from '@libs/compose'; import getCurrentPosition from '@libs/getCurrentPosition'; import * as GooglePlacesUtils from '@libs/GooglePlacesUtils'; -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'; import CurrentLocationButton from './CurrentLocationButton'; diff --git a/src/components/AmountTextInput.js b/src/components/AmountTextInput.js index bd88712432a8..25e1ce6f05ec 100644 --- a/src/components/AmountTextInput.js +++ b/src/components/AmountTextInput.js @@ -1,6 +1,7 @@ import PropTypes from 'prop-types'; import React from 'react'; -import useThemeStyles from '@styles/useThemeStyles'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import refPropTypes from './refPropTypes'; import TextInput from './TextInput'; @@ -27,6 +28,12 @@ const propTypes = { /** Function to call when selection in text input is changed */ onSelectionChange: PropTypes.func, + /** Style for the input */ + style: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + + /** Style for the container */ + containerStyles: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.object), PropTypes.object]), + /** Function to call to handle key presses in the text input */ onKeyPress: PropTypes.func, }; @@ -36,16 +43,19 @@ const defaultProps = { selection: undefined, onSelectionChange: () => {}, onKeyPress: () => {}, + style: {}, + containerStyles: {}, }; function AmountTextInput(props) { const styles = useThemeStyles(); + const StyleUtils = useStyleUtils(); return ( ); } diff --git a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js index 1e2d18bc4691..6161ba140726 100644 --- a/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js +++ b/src/components/AnchorForAttachmentsOnly/BaseAnchorForAttachmentsOnly.js @@ -58,7 +58,7 @@ function BaseAnchorForAttachmentsOnly(props) { onPressOut={props.onPressOut} onLongPress={(event) => showContextMenuForReport(event, anchor, report.reportID, action, checkIfContextMenuActive, ReportUtils.isArchivedRoom(report))} accessibilityLabel={fileName} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} > (linkRef = el)} style={StyleSheet.flatten([style, defaultTextStyle])} - role={CONST.ACCESSIBILITY_ROLE.LINK} + role={CONST.ROLE.LINK} hrefAttrs={{ rel, target: isEmail || !linkProps.href ? '_self' : target, 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 fbc49590d5ae..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 = { @@ -81,7 +81,7 @@ function CarouselItem({item, isFocused, onPress}) { {children} 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 22bcf259ed77..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'; @@ -28,7 +28,7 @@ function AttachmentViewImage({source, file, isAuthTokenRequired, loadComplete, o onPress={onPress} disabled={loadComplete} style={[styles.flex1, styles.flexRow, styles.alignSelfStretch]} - role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={file.name || translate('attachmentView.unknownFilename')} > {children} diff --git a/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js b/src/components/Attachments/AttachmentView/AttachmentViewImage/index.native.js index fc443e5ea17b..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'; @@ -36,7 +36,7 @@ function AttachmentViewImage({source, file, isAuthTokenRequired, isFocused, isUs onPress={onPress} disabled={loadComplete} style={[styles.flex1, styles.flexRow, styles.alignSelfStretch]} - role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={file.name || translate('attachmentView.unknownFilename')} > {children} 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..4ad3d45544b0 100644 --- a/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx +++ b/src/components/AutoCompleteSuggestions/BaseAutoCompleteSuggestions.tsx @@ -5,8 +5,8 @@ import {View} from 'react-native'; import {ScrollView} from 'react-native-gesture-handler'; import Animated, {Easing, FadeOutDown, useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated'; 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'; 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.tsx b/src/components/AutoUpdateTime.tsx index 7b584e236b37..258bdb281eb2 100644 --- a/src/components/AutoUpdateTime.tsx +++ b/src/components/AutoUpdateTime.tsx @@ -4,8 +4,8 @@ */ 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 {Timezone} from '@src/types/onyx/PersonalDetails'; import Text from './Text'; import withLocalize, {WithLocalizeProps} 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 dcb0470c5ee5..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'; @@ -413,7 +413,7 @@ function AvatarCropModal(props) { onLayout={initializeSliderContainer} onPressIn={(e) => runOnUI(sliderOnPress)(e.nativeEvent.locationX)} accessibilityLabel="slider" - role={CONST.ACCESSIBILITY_ROLE.ADJUSTABLE} + role={CONST.ROLE.SLIDER} > {shouldShowSubscriptAvatar ? ( ReportUtils.navigateToDetailsPage(report)} style={[styles.flexRow, styles.alignItemsCenter, styles.flex1]} accessibilityLabel={title} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} > {headerView} diff --git a/src/components/AvatarWithImagePicker.js b/src/components/AvatarWithImagePicker.js index eabcd3aa85c5..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'; @@ -297,7 +297,7 @@ function AvatarWithImagePicker({ setIsMenuVisible((prev) => !prev)} - role={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} + accessibilityRole={CONST.ACCESSIBILITY_ROLE.IMAGEBUTTON} accessibilityLabel={translate('avatarWithImagePicker.editImage')} disabled={isAvatarCropModalOpen} ref={anchorRef} 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 82212c66db04..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'; @@ -47,7 +47,7 @@ function Badge({success = false, error = false, pressable = false, text, environ 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 9cbd19e03dc7..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(); } @@ -293,7 +300,7 @@ function Button( ]} id={id} accessibilityLabel={accessibilityLabel} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} hoverDimmingValue={1} > {renderContent()} 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 570d00559af8..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'; @@ -32,7 +32,7 @@ function CollapsibleSection({title, children}: CollapsibleSectionProps) { 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 4d43ec3d93e0..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'; @@ -23,7 +23,7 @@ function CurrencySymbolButton({onCurrencyButtonPress, currencySymbol}) { {currencySymbol} 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/index.tsx b/src/components/CustomStatusBar/index.tsx index 3b5022c60898..2c1af898786d 100644 --- a/src/components/CustomStatusBar/index.tsx +++ b/src/components/CustomStatusBar/index.tsx @@ -1,30 +1,19 @@ import {EventListenerCallback, NavigationContainerEventMap} from '@react-navigation/native'; -import PropTypes from 'prop-types'; import React, {useCallback, useContext, useEffect} from 'react'; +import useTheme from '@hooks/useTheme'; 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; + isNested: boolean; }; // eslint-disable-next-line react/function-component-definition -const CustomStatusBar: CustomStatusBarType = ({isNested = false}) => { +function CustomStatusBar({isNested = false}: CustomStatusBarProps) { const {isRootStatusBarDisabled, disableRootStatusBar} = useContext(CustomStatusBarContext); const theme = useTheme(); @@ -83,9 +72,8 @@ const CustomStatusBar: CustomStatusBarType = ({isNested = false}) => { } return ; -}; +} CustomStatusBar.displayName = 'CustomStatusBar'; -CustomStatusBar.propTypes = propTypes; export default CustomStatusBar; diff --git a/src/components/CustomStatusBar/updateStatusBarAppearance/types.ts b/src/components/CustomStatusBar/updateStatusBarAppearance/types.ts index 3d16b5944a31..823f0059eccf 100644 --- a/src/components/CustomStatusBar/updateStatusBarAppearance/types.ts +++ b/src/components/CustomStatusBar/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 10a53dc25bbb..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'; @@ -76,7 +76,7 @@ function DatePicker({containerStyles, defaultValue, disabled, errorText, inputID icon={Expensicons.Calendar} label={label} accessibilityLabel={label} - role={CONST.ACCESSIBILITY_ROLE.TEXT} + role={CONST.ROLE.PRESENTATION} value={value || selectedDate || ''} placeholder={placeholder || translate('common.dateFormat')} errorText={errorText} 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/DeeplinkWrapper/index.website.js b/src/components/DeeplinkWrapper/index.website.js index 166818e4ae27..d81c99657dd8 100644 --- a/src/components/DeeplinkWrapper/index.website.js +++ b/src/components/DeeplinkWrapper/index.website.js @@ -16,6 +16,8 @@ const propTypes = { children: PropTypes.node.isRequired, /** User authentication status */ isAuthenticated: PropTypes.bool.isRequired, + /** The auto authentication status */ + autoAuthState: PropTypes.string, }; function isMacOSWeb() { @@ -36,7 +38,7 @@ function promptToOpenInDesktopApp() { App.beginDeepLinkRedirect(!isMagicLink); } } -function DeeplinkWrapper({children, isAuthenticated}) { +function DeeplinkWrapper({children, isAuthenticated, autoAuthState}) { const [currentScreen, setCurrentScreen] = useState(); const [hasShownPrompt, setHasShownPrompt] = useState(false); const removeListener = useRef(); @@ -69,7 +71,7 @@ function DeeplinkWrapper({children, isAuthenticated}) { return routeRegex.test(window.location.pathname); }); // Making a few checks to exit early before checking authentication status - if (!isMacOSWeb() || isUnsupportedDeeplinkRoute || CONFIG.ENVIRONMENT === CONST.ENVIRONMENT.DEV || hasShownPrompt) { + if (!isMacOSWeb() || isUnsupportedDeeplinkRoute || hasShownPrompt || CONFIG.ENVIRONMENT === CONST.ENVIRONMENT.DEV || autoAuthState === CONST.AUTO_AUTH_STATE.NOT_STARTED) { return; } // We want to show the prompt immediately if the user is already authenticated. @@ -92,7 +94,7 @@ function DeeplinkWrapper({children, isAuthenticated}) { promptToOpenInDesktopApp(); setHasShownPrompt(true); } - }, [currentScreen, hasShownPrompt, isAuthenticated]); + }, [currentScreen, hasShownPrompt, isAuthenticated, autoAuthState]); return children; } 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 8738f210276e..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'; @@ -41,7 +41,7 @@ function CategoryShortcutButton(props) { onHoverOut={() => setIsHighlighted(false)} style={({pressed}) => [StyleUtils.getButtonBackgroundColorStyle(getButtonState(false, pressed)), styles.categoryShortcutButton, isHighlighted && styles.emojiItemHighlighted]} accessibilityLabel={`emojiPicker.headers.${props.code}`} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} > {({hovered, pressed}) => ( diff --git a/src/components/EmojiPicker/EmojiPickerMenu/index.js b/src/components/EmojiPicker/EmojiPickerMenu/index.js index 95db6eb41167..36594dabcd30 100755 --- a/src/components/EmojiPicker/EmojiPickerMenu/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenu/index.js @@ -11,6 +11,8 @@ import EmojiSkinToneList from '@components/EmojiPicker/EmojiSkinToneList'; import Text from '@components/Text'; import TextInput from '@components/TextInput'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import useWindowDimensions from '@hooks/useWindowDimensions'; import * as Browser from '@libs/Browser'; import canFocusInputOnScreenFocus from '@libs/canFocusInputOnScreenFocus'; @@ -18,8 +20,6 @@ import compose from '@libs/compose'; import * as EmojiUtils from '@libs/EmojiUtils'; import isEnterWhileComposition from '@libs/KeyboardShortcut/isEnterWhileComposition'; import * as ReportUtils from '@libs/ReportUtils'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import * as User from '@userActions/User'; import CONST from '@src/CONST'; import ONYXKEYS from '@src/ONYXKEYS'; @@ -485,7 +485,7 @@ function EmojiPickerMenu(props) { 0} /> diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js index ae2cdf46dfc0..52d4a0db8812 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.js @@ -103,7 +103,7 @@ class EmojiPickerMenuItem extends PureComponent { this.props.themeStyles.emojiItem, ]} accessibilityLabel={this.props.emoji} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} > {this.props.emoji} diff --git a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js index 7934cc0f03d4..1726ff5b6543 100644 --- a/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js +++ b/src/components/EmojiPicker/EmojiPickerMenuItem/index.native.js @@ -80,7 +80,7 @@ class EmojiPickerMenuItem extends PureComponent { this.props.themeStyles.emojiItem, ]} accessibilityLabel={this.props.emoji} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} > {this.props.emoji} diff --git a/src/components/EmojiPicker/EmojiSkinToneList.js b/src/components/EmojiPicker/EmojiSkinToneList.js index 25fc9ad0836a..2b574b0b533f 100644 --- a/src/components/EmojiPicker/EmojiSkinToneList.js +++ b/src/components/EmojiPicker/EmojiSkinToneList.js @@ -6,7 +6,7 @@ import * as Emojis from '@assets/emojis'; import PressableWithoutFeedback from '@components/Pressable/PressableWithoutFeedback'; import Text from '@components/Text'; import withLocalize, {withLocalizePropTypes} from '@components/withLocalize'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; import EmojiPickerMenuItem from './EmojiPickerMenuItem'; import getSkinToneEmojiFromIndex from './getSkinToneEmojiFromIndex'; @@ -49,7 +49,7 @@ function EmojiSkinToneList(props) { onPress={toggleIsSkinToneListVisible} style={[styles.flexRow, styles.alignSelfCenter, styles.justifyContentStart, styles.alignItemsCenter]} accessibilityLabel={props.translate('emojiPicker.skinTonePickerLabel')} - role={CONST.ACCESSIBILITY_ROLE.BUTTON} + role={CONST.ROLE.BUTTON} > {currentSkinTone.code} diff --git a/src/components/EmojiSuggestions.tsx b/src/components/EmojiSuggestions.tsx index 01f840677e5e..2fcf8e827b4e 100644 --- a/src/components/EmojiSuggestions.tsx +++ b/src/components/EmojiSuggestions.tsx @@ -1,10 +1,10 @@ import React, {ReactElement, useCallback} from 'react'; import {View} from 'react-native'; +import useStyleUtils from '@hooks/useStyleUtils'; +import useThemeStyles from '@hooks/useThemeStyles'; import type {SimpleEmoji} from '@libs/EmojiTrie'; import * as EmojiUtils from '@libs/EmojiUtils'; import getStyledTextArray from '@libs/GetStyledTextArray'; -import useStyleUtils from '@styles/useStyleUtils'; -import useThemeStyles from '@styles/useThemeStyles'; import AutoCompleteSuggestions from './AutoCompleteSuggestions'; import Text from './Text'; diff --git a/src/components/EnvironmentBadge.tsx b/src/components/EnvironmentBadge.tsx index a3f321072988..6babbf119445 100644 --- a/src/components/EnvironmentBadge.tsx +++ b/src/components/EnvironmentBadge.tsx @@ -1,7 +1,7 @@ import React from 'react'; import useEnvironment from '@hooks/useEnvironment'; +import useThemeStyles from '@hooks/useThemeStyles'; import * as Environment from '@libs/Environment/Environment'; -import useThemeStyles from '@styles/useThemeStyles'; import CONST from '@src/CONST'; import pkg from '../../package.json'; import Badge from './Badge'; diff --git a/src/components/ExceededCommentLength.js b/src/components/ExceededCommentLength.js index 6353bdf40283..3fd6688944f7 100644 --- a/src/components/ExceededCommentLength.js +++ b/src/components/ExceededCommentLength.js @@ -1,52 +1,23 @@ -import {debounce} from 'lodash'; import PropTypes from 'prop-types'; -import React, {useEffect, useMemo, useState} from 'react'; -import {withOnyx} from 'react-native-onyx'; +import React from 'react'; import useLocalize from '@hooks/useLocalize'; -import * as ReportUtils from '@libs/ReportUtils'; -import useThemeStyles from '@styles/useThemeStyles'; +import useThemeStyles from '@hooks/useThemeStyles'; import CONST from '@src/CONST'; -import ONYXKEYS from '@src/ONYXKEYS'; import Text from './Text'; const propTypes = { - /** Report ID to get the comment from (used in withOnyx) */ - // eslint-disable-next-line react/no-unused-prop-types - reportID: PropTypes.string.isRequired, - - /** Text Comment */ - comment: PropTypes.string, - - /** Update UI on parent when comment length is exceeded */ - onExceededMaxCommentLength: PropTypes.func.isRequired, + shouldShowError: PropTypes.bool.isRequired, }; -const defaultProps = { - comment: '', -}; +const defaultProps = {}; function ExceededCommentLength(props) { const styles = useThemeStyles(); const {numberFormat, translate} = useLocalize(); - const [commentLength, setCommentLength] = useState(0); - const updateCommentLength = useMemo( - () => - 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} /> ) : (