diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..b8991f4 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,15 @@ +node_modules +ios +android +.vscode +.husky +package.json +yarn.lock +*.graphql +src/translations/* +src/resources/fonts/* +src/selection.json +.eslintrc.js +*.png +src/**/*.typegen.ts +__generated__ diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 0000000..0744343 --- /dev/null +++ b/.eslintrc.cjs @@ -0,0 +1,275 @@ +module.exports = { + root: true, + env: { + es6: true + }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:consistent-default-export-name/fixed', + 'plugin:prettier/recommended' + ], + plugins: [ + 'react', + 'react-native', + 'eslint-plugin-jsdoc', + '@typescript-eslint', + 'react-hooks', + 'unicorn', + 'import', + 'prettier' + ], + settings: { + ecmaVersion: 'latest', + react: { + version: 'detect' + } + }, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { + jsx: true + } + }, + rules: { + 'import/no-duplicates': 'error', + 'import/no-self-import': 'error', + 'import/no-extraneous-dependencies': [ + 'error', + { + peerDependencies: true, + devDependencies: [ + '**/*.test.[jt]s', + '**/*.spec.[jt]s', + '**/*.test.[jt]sx', + '**/*.spec.[jt]sx', + 'ReactotronConfig.js' + ] + } + ], + 'sort-imports': ['error', {ignoreCase: true, ignoreDeclarationSort: true}], + 'import/order': [ + 'error', + { + 'newlines-between': 'always', + pathGroups: [ + { + pattern: '$/**', + group: 'internal' + } + ], + pathGroupsExcludedImportTypes: ['builtin'], + groups: [ + ['builtin', 'external'], + ['internal'], + ['parent', 'sibling', 'index'], + 'unknown' + ] + } + ], + 'import/no-cycle': [ + 'error', + { + maxDepth: '∞', + ignoreExternal: true + } + ], + /* additional rules previously used */ + 'react/react-in-jsx-scope': 'off', + 'react/jsx-curly-brace-presence': [ + 'warn', + {props: 'never', children: 'never'} + ], + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': [ + 'error', + { + additionalHooks: '(useRecoilCallback|useRecoilTransaction_UNSTABLE)' + } + ], + 'react/display-name': 'off', + 'react/prop-types': 'off', + 'react/no-string-refs': 'warn', + 'react/no-unescaped-entities': 'warn', + 'react-native/no-unused-styles': 'error', + 'react/no-children-prop': 'error', + 'react/no-this-in-sfc': 'error', + 'react/no-unused-state': 'error', + 'react/function-component-definition': [ + 'error', + { + namedComponents: 'arrow-function', + unnamedComponents: 'arrow-function' + } + ], + 'react/no-access-state-in-setstate': 'error', + 'default-case': 'error', + eqeqeq: ['warn', 'smart'], + 'guard-for-in': 'error', + 'jsdoc/check-alignment': 'error', + 'jsdoc/check-indentation': 'error', + 'jsdoc/tag-lines': ['error', 'never'], + 'prefer-const': 'error', + 'no-unused-vars': 'warn', + 'newline-before-return': 'warn', + 'no-unsafe-optional-chaining': 'off', + 'no-extra-boolean-cast': 'off', // redundant double negation + 'no-useless-escape': 'off', // cleans up regexs, + 'no-duplicate-case': 'off', // spots duplicate case statements + 'no-case-declarations': 'off', // prevents hoisting of variables in case statements + 'no-async-promise-executor': 'off', // good suggestion for a code smell + 'no-empty-pattern': 'off', // unexpected empty object + 'no-prototype-builtins': 'off', // this can be dangerous + 'no-constant-condition': ['error', {checkLoops: false}], // this is fine + 'no-bitwise': 'error', + 'no-caller': 'error', + 'no-console': [ + 'error', + { + allow: [ + 'info', // prefer info to log + 'warn', + 'dir', + 'timeLog', + 'assert', + 'clear', + 'count', + 'countReset', + 'group', + 'groupEnd', + 'table', + 'dirxml', + 'error', + 'groupCollapsed', + 'Console', + 'profile', + 'profileEnd', + 'timeStamp', + 'context' + ] + } + ], + 'no-eval': 'error', + 'no-new-wrappers': 'error', + 'no-redeclare': 'error', + 'no-var': 'error', + radix: 'error', + 'spaced-comment': [ + 'error', + 'always', + { + markers: ['/'] + } + ], + 'consistent-default-export-name/default-export-match-filename': 'error', + 'consistent-default-export-name/default-import-match-filename': 'error', + 'unicorn/filename-case': [ + 'error', + { + cases: { + camelCase: true, + pascalCase: true + } + } + ], + 'eol-last': ['error', 'always'] + }, + overrides: [ + { + files: ['*.ts', '*.tsx'], + extends: [ + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking' + ], + plugins: ['@typescript-eslint/eslint-plugin'], + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module' + }, + rules: { + '@typescript-eslint/no-use-before-define': [ + 'error', + { + enums: true, + typedefs: true, + ignoreTypeReferences: true, + functions: true, + classes: true, + variables: true, + allowNamedExports: true + } + ], + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-unused-vars': 'warn', + /* rules introduced by the recommened plugins that are to be addressed in future PRs */ + '@typescript-eslint/no-explicit-any': 'off', // todo temporary disabled, should be revised + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', // this is return types but only on module boundaries vs @typescript-eslint/explicit-function-return-type + '@typescript-eslint/unbound-method': 'off', // this rule is failing on things I don't get + '@typescript-eslint/no-unsafe-member-access': 'off', // lodash import causing this + '@typescript-eslint/no-unsafe-call': 'off', // lodash as well + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/require-await': 'off', // todo temporary disabled, should be revised + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-empty-interface': 'error', + '@typescript-eslint/ban-types': 'off', // this spots using Number instead of number + '@typescript-eslint/restrict-plus-operands': 'off', // good rule + '@typescript-eslint/restrict-template-expressions': 'off', // todo temporary disabled, should be revised should be easy to fix + '@typescript-eslint/prefer-regexp-exec': 'off', // swaps how regex and string.match are used + '@typescript-eslint/prefer-as-const': 'error', + '@typescript-eslint/no-unnecessary-type-assertion': 'error', // removes redundant code + '@typescript-eslint/await-thenable': 'off', // await functions that aren't promises + '@typescript-eslint/no-inferrable-types': 'off', // don't added types for easy to define things + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unnecessary-type-constraint': 'error', + '@typescript-eslint/member-ordering': 'error', + '@typescript-eslint/prefer-namespace-keyword': 'error', + '@typescript-eslint/consistent-type-definitions': ['error', 'type'], + '@typescript-eslint/no-unsafe-enum-comparison': 'off', // todo temporary disabled, should be revised + '@typescript-eslint/no-redundant-type-constituents': 'off', // todo temporary disabled, should be revised + '@typescript-eslint/ban-ts-comment': [ + 'error', + { + 'ts-ignore': 'allow-with-description' + } + ], + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: 'variable', + types: ['boolean'], + format: ['PascalCase'], + prefix: ['is', 'should', 'has', 'can', 'did', 'will', 'was'] + }, + { + selector: 'variable', + modifiers: ['destructured'], + format: null + }, + { + selector: 'variable', + format: ['camelCase', 'PascalCase', 'UPPER_CASE'] + }, + {selector: 'function', format: ['camelCase']}, + {selector: 'class', format: ['PascalCase']}, + {selector: 'enum', format: ['PascalCase']}, + {selector: 'enumMember', format: ['PascalCase', 'UPPER_CASE']}, + {selector: 'interface', format: ['PascalCase']}, + {selector: 'typeAlias', format: ['PascalCase']}, + {selector: 'parameter', format: ['camelCase']} + ] + } + }, + { + files: ['./**/*.test.ts'], + rules: { + '@typescript-eslint/naming-convention': 'off' + } + } + ] +}; diff --git a/.eslintrc.js b/.eslintrc.js index 187894b..6219fb2 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,4 +1,275 @@ module.exports = { root: true, - extends: '@react-native', + env: { + es6: true, + }, + extends: [ + 'eslint:recommended', + 'plugin:react/recommended', + 'plugin:react-hooks/recommended', + 'plugin:consistent-default-export-name/fixed', + 'plugin:prettier/recommended', + ], + plugins: [ + 'react', + 'react-native', + 'eslint-plugin-jsdoc', + '@typescript-eslint', + 'react-hooks', + 'unicorn', + 'import', + 'prettier', + ], + settings: { + ecmaVersion: 'latest', + react: { + version: 'detect', + }, + }, + parserOptions: { + ecmaVersion: 'latest', + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + rules: { + 'import/no-duplicates': 'error', + 'import/no-self-import': 'error', + 'import/no-extraneous-dependencies': [ + 'error', + { + peerDependencies: true, + devDependencies: [ + '**/*.test.[jt]s', + '**/*.spec.[jt]s', + '**/*.test.[jt]sx', + '**/*.spec.[jt]sx', + 'ReactotronConfig.js', + ], + }, + ], + 'sort-imports': ['error', {ignoreCase: true, ignoreDeclarationSort: true}], + 'import/order': [ + 'error', + { + 'newlines-between': 'always', + pathGroups: [ + { + pattern: '$/**', + group: 'internal', + }, + ], + pathGroupsExcludedImportTypes: ['builtin'], + groups: [ + ['builtin', 'external'], + ['internal'], + ['parent', 'sibling', 'index'], + 'unknown', + ], + }, + ], + 'import/no-cycle': [ + 'error', + { + maxDepth: '∞', + ignoreExternal: true, + }, + ], + /* additional rules previously used */ + 'react/react-in-jsx-scope': 'off', + 'react/jsx-curly-brace-presence': [ + 'warn', + {props: 'never', children: 'never'}, + ], + 'react-hooks/rules-of-hooks': 'error', + 'react-hooks/exhaustive-deps': [ + 'error', + { + additionalHooks: '(useRecoilCallback|useRecoilTransaction_UNSTABLE)', + }, + ], + 'react/display-name': 'off', + 'react/prop-types': 'off', + 'react/no-string-refs': 'warn', + 'react/no-unescaped-entities': 'warn', + 'react-native/no-unused-styles': 'error', + 'react/no-children-prop': 'error', + 'react/no-this-in-sfc': 'error', + 'react/no-unused-state': 'error', + 'react/function-component-definition': [ + 'error', + { + namedComponents: 'arrow-function', + unnamedComponents: 'arrow-function', + }, + ], + 'react/no-access-state-in-setstate': 'error', + 'default-case': 'error', + eqeqeq: ['warn', 'smart'], + 'guard-for-in': 'error', + 'jsdoc/check-alignment': 'error', + 'jsdoc/check-indentation': 'error', + 'jsdoc/tag-lines': ['error', 'never'], + 'prefer-const': 'error', + 'no-unused-vars': 'warn', + 'newline-before-return': 'warn', + 'no-unsafe-optional-chaining': 'off', + 'no-extra-boolean-cast': 'off', // redundant double negation + 'no-useless-escape': 'off', // cleans up regexs, + 'no-duplicate-case': 'off', // spots duplicate case statements + 'no-case-declarations': 'off', // prevents hoisting of variables in case statements + 'no-async-promise-executor': 'off', // good suggestion for a code smell + 'no-empty-pattern': 'off', // unexpected empty object + 'no-prototype-builtins': 'off', // this can be dangerous + 'no-constant-condition': ['error', {checkLoops: false}], // this is fine + 'no-bitwise': 'error', + 'no-caller': 'error', + 'no-console': [ + 'error', + { + allow: [ + 'info', // prefer info to log + 'warn', + 'dir', + 'timeLog', + 'assert', + 'clear', + 'count', + 'countReset', + 'group', + 'groupEnd', + 'table', + 'dirxml', + 'error', + 'groupCollapsed', + 'Console', + 'profile', + 'profileEnd', + 'timeStamp', + 'context', + ], + }, + ], + 'no-eval': 'error', + 'no-new-wrappers': 'error', + 'no-redeclare': 'error', + 'no-var': 'error', + radix: 'error', + 'spaced-comment': [ + 'error', + 'always', + { + markers: ['/'], + }, + ], + 'consistent-default-export-name/default-export-match-filename': 'error', + 'consistent-default-export-name/default-import-match-filename': 'error', + 'unicorn/filename-case': [ + 'error', + { + cases: { + camelCase: true, + pascalCase: true, + }, + }, + ], + 'eol-last': ['error', 'always'], + }, + overrides: [ + { + files: ['*.ts', '*.tsx'], + extends: [ + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + ], + plugins: ['@typescript-eslint/eslint-plugin'], + parser: '@typescript-eslint/parser', + parserOptions: { + project: './tsconfig.json', + tsconfigRootDir: __dirname, + sourceType: 'module', + }, + rules: { + '@typescript-eslint/no-use-before-define': [ + 'error', + { + enums: true, + typedefs: true, + ignoreTypeReferences: true, + functions: true, + classes: true, + variables: true, + allowNamedExports: true, + }, + ], + '@typescript-eslint/no-empty-function': 'off', + '@typescript-eslint/no-unused-vars': 'warn', + /* rules introduced by the recommened plugins that are to be addressed in future PRs */ + '@typescript-eslint/no-explicit-any': 'off', // todo temporary disabled, should be revised + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', // this is return types but only on module boundaries vs @typescript-eslint/explicit-function-return-type + '@typescript-eslint/unbound-method': 'off', // this rule is failing on things I don't get + '@typescript-eslint/no-unsafe-member-access': 'off', // lodash import causing this + '@typescript-eslint/no-unsafe-call': 'off', // lodash as well + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/require-await': 'off', // todo temporary disabled, should be revised + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-empty-interface': 'error', + '@typescript-eslint/ban-types': 'off', // this spots using Number instead of number + '@typescript-eslint/restrict-plus-operands': 'off', // good rule + '@typescript-eslint/restrict-template-expressions': 'off', // todo temporary disabled, should be revised should be easy to fix + '@typescript-eslint/prefer-regexp-exec': 'off', // swaps how regex and string.match are used + '@typescript-eslint/prefer-as-const': 'error', + '@typescript-eslint/no-unnecessary-type-assertion': 'error', // removes redundant code + '@typescript-eslint/await-thenable': 'off', // await functions that aren't promises + '@typescript-eslint/no-inferrable-types': 'off', // don't added types for easy to define things + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unnecessary-type-constraint': 'error', + '@typescript-eslint/member-ordering': 'error', + '@typescript-eslint/prefer-namespace-keyword': 'error', + '@typescript-eslint/consistent-type-definitions': ['error', 'type'], + '@typescript-eslint/no-unsafe-enum-comparison': 'off', // todo temporary disabled, should be revised + '@typescript-eslint/no-redundant-type-constituents': 'off', // todo temporary disabled, should be revised + '@typescript-eslint/ban-ts-comment': [ + 'error', + { + 'ts-ignore': 'allow-with-description', + }, + ], + '@typescript-eslint/naming-convention': [ + 'error', + { + selector: 'variable', + types: ['boolean'], + format: ['PascalCase'], + prefix: ['is', 'should', 'has', 'can', 'did', 'will', 'was'], + }, + { + selector: 'variable', + modifiers: ['destructured'], + format: null, + }, + { + selector: 'variable', + format: ['camelCase', 'PascalCase', 'UPPER_CASE'], + }, + {selector: 'function', format: ['camelCase']}, + {selector: 'class', format: ['PascalCase']}, + {selector: 'enum', format: ['PascalCase']}, + {selector: 'enumMember', format: ['PascalCase', 'UPPER_CASE']}, + {selector: 'interface', format: ['PascalCase']}, + {selector: 'typeAlias', format: ['PascalCase']}, + {selector: 'parameter', format: ['camelCase']}, + ], + }, + }, + { + files: ['./**/*.test.ts'], + rules: { + '@typescript-eslint/naming-convention': 'off', + }, + }, + ], }; diff --git a/App.tsx b/App.tsx index 125fe1b..0d7a433 100644 --- a/App.tsx +++ b/App.tsx @@ -1,118 +1,29 @@ -/** - * Sample React Native App - * https://github.com/facebook/react-native - * - * @format - */ - import React from 'react'; -import type {PropsWithChildren} from 'react'; -import { - SafeAreaView, - ScrollView, - StatusBar, - StyleSheet, - Text, - useColorScheme, - View, -} from 'react-native'; +import {SafeAreaView} from 'react-native'; +import {CardScreen} from './src/CardScreen/CardScreen'; import { - Colors, - DebugInstructions, - Header, - LearnMoreLinks, - ReloadInstructions, -} from 'react-native/Libraries/NewAppScreen'; - -type SectionProps = PropsWithChildren<{ - title: string; -}>; + FrameCardTokenizationFailedEvent, + FrameCardTokenizedEvent, +} from './src/CardScreen/types/types'; +import {PUBLIC_KEY} from './src/utils/constants'; -function Section({children, title}: SectionProps): React.JSX.Element { - const isDarkMode = useColorScheme() === 'dark'; +const App = (): React.JSX.Element => { return ( - - - {title} - - - {children} - - - ); -} - -function App(): React.JSX.Element { - const isDarkMode = useColorScheme() === 'dark'; - - const backgroundStyle = { - backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, - }; - - return ( - - + - -
- -
- Edit App.tsx to change this - screen and then come back to see your edits. -
-
- -
-
- -
-
- Read the docs to discover what to do next: -
- -
- ); -} - -const styles = StyleSheet.create({ - sectionContainer: { - marginTop: 32, - paddingHorizontal: 24, - }, - sectionTitle: { - fontSize: 24, - fontWeight: '600', - }, - sectionDescription: { - marginTop: 8, - fontSize: 18, - fontWeight: '400', - }, - highlight: { - fontWeight: '700', - }, -}); +}; export default App; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 3a94308..c9cae53 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1164,6 +1164,8 @@ PODS: - React-logger (= 0.74.3) - React-perflogger (= 0.74.3) - React-utils (= 0.74.3) + - RNSVG (15.4.0): + - React-Core - SocketRocket (0.7.0) - Yoga (0.0.0) @@ -1223,6 +1225,7 @@ DEPENDENCIES: - React-runtimescheduler (from `../node_modules/react-native/ReactCommon/react/renderer/runtimescheduler`) - React-utils (from `../node_modules/react-native/ReactCommon/react/utils`) - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`) + - RNSVG (from `../node_modules/react-native-svg`) - Yoga (from `../node_modules/react-native/ReactCommon/yoga`) SPEC REPOS: @@ -1337,6 +1340,8 @@ EXTERNAL SOURCES: :path: "../node_modules/react-native/ReactCommon/react/utils" ReactCommon: :path: "../node_modules/react-native/ReactCommon" + RNSVG: + :path: "../node_modules/react-native-svg" Yoga: :path: "../node_modules/react-native/ReactCommon/yoga" @@ -1394,6 +1399,7 @@ SPEC CHECKSUMS: React-runtimescheduler: 0c80752bceb80924cb8a4babc2a8e3ed70d41e87 React-utils: a06061b3887c702235d2dac92dacbd93e1ea079e ReactCommon: f00e436b3925a7ae44dfa294b43ef360fbd8ccc4 + RNSVG: cb24fb322de8c1ebf59904e7aca0447bb8dbed5a SocketRocket: abac6f5de4d4d62d24e11868d7a2f427e0ef940d Yoga: 04f1db30bb810187397fa4c37dd1868a27af229c diff --git a/package.json b/package.json index fef3924..aa60e5e 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,22 @@ "start": "react-native start", "start:android": "react-native run-android", "start:ios": "react-native run-ios", - "lint": "eslint .", - "test": "jest" + "tsc": "tsc --project tsconfig.json --noEmit", + "eslint": "eslint -c .eslintrc.cjs --ext .js,.jsx,.ts,tsx src --max-warnings=0", + "eslint:fix": "eslint --fix -c .eslintrc.cjs --ext .js,.jsx,.ts,tsx src", + "lint": "yarn tsc && yarn eslint", + "test": "jest", + "clean-install": "git clean -xfd && yarn install" }, "dependencies": { + "@emotion/react": "^11.11.4", + "@hookform/resolvers": "^3.9.0", "react": "18.2.0", - "react-native": "0.74.3" + "react-hook-form": "^7.52.1", + "react-native": "0.74.3", + "react-native-animation-hooks": "^1.0.1", + "react-native-svg": "^15.4.0", + "yup": "^1.4.0" }, "devDependencies": { "@babel/core": "^7.20.0", @@ -25,6 +35,15 @@ "@types/react-test-renderer": "^18.0.0", "babel-jest": "^29.6.3", "eslint": "^8.19.0", + "eslint-config-prettier": "^8.10.0", + "eslint-plugin-consistent-default-export-name": "^0.0.15", + "eslint-plugin-import": "^2.29.1", + "eslint-plugin-jsdoc": "^48.2.12", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "eslint-plugin-react-native": "^4.1.0", + "eslint-plugin-unicorn": "^44.0.2", "jest": "^29.6.3", "prettier": "2.8.8", "react-test-renderer": "18.2.0", diff --git a/src/CardScreen/CardScreen.tsx b/src/CardScreen/CardScreen.tsx new file mode 100644 index 0000000..fffe0ed --- /dev/null +++ b/src/CardScreen/CardScreen.tsx @@ -0,0 +1,261 @@ +import React, {FC, useState} from 'react'; +import {boolean, object, string} from 'yup'; +import {SafeAreaView, ScrollView, StyleSheet, View} from 'react-native'; +import * as yup from 'yup'; + +import {Text} from '../ui/components/Text'; +import {Button} from '../ui/components/Button'; +import {Form, FormTextInput} from '../ui/components/Form'; +import {FormCheckbox} from '../ui/components/Form/FormCheckbox'; +import {Modal} from '../ui/components/Modal'; +import { + FrameCardTokenizationFailedEvent, + FrameCardTokenizedEvent, + TokenizationParams, +} from './types/types'; +import {tokenize} from '../utils/http'; +declare module 'yup' { + // eslint-disable-next-line @typescript-eslint/consistent-type-definitions + interface StringSchema { + cardExpiry(): StringSchema; + } +} +// Adding a custom method to yup for expiry date validation +yup.addMethod(string, 'cardExpiry', function (message: any) { + return this.test('cardExpiry', message, function (expiryDate: any) { + const {path, createError} = this; + if (!expiryDate) { + return false; + } + const currentYear = new Date().getFullYear().toString().slice(2); + const currentMonth = new Date().getMonth() + 1; + const [month, year] = expiryDate.split('/'); + + if (!month || !year) { + return createError({path, message: message || 'invalid expiry date'}); + } + if (month.length !== 2 || year.length !== 2) { + return createError({path, message: message || 'MM/YY format required'}); + } + if (month > 12 || month < 1) { + return createError({path, message: message || 'Month is invalid'}); + } + + if (year === currentYear && month < new Date().getMonth()) { + return createError({path, message: message || 'Month is in past'}); + } + if (year < currentYear) { + return createError({path, message: message || 'Year is in the past'}); + } + if ( + (month < currentMonth && year < currentYear) || + (month > currentMonth && year < currentYear) + ) { + return createError({path, message: message || 'Date is already expired'}); + } + // if (month > currentMonth && year === currentYear) { + // return createError({path, message: message || 'Year is in past'}); + // } + + return true; // Valid expiry date + }); +}); +const styles = StyleSheet.create({ + container: { + margin: 20, + }, + row: { + marginTop: 20, + flexDirection: 'row', + justifyContent: 'space-between', + }, +}); + +type CardScreenProps = { + checkoutKey: string; + cardTokenized: (e: FrameCardTokenizedEvent) => void; + cardTokenizationFailed?: (e: FrameCardTokenizationFailedEvent) => void; +}; + +export const CardScreen: FC = ({ + checkoutKey, + cardTokenized, + cardTokenizationFailed, +}) => { + const [isSubmitting, setIsSubmitting] = useState(false); + const [shouldShow3dsModal, setShouldShow3dsModal] = useState(false); + const [isError, setIsError] = useState(false); + const [error, setError] = useState(null); + + const onPay = async (data: { + fullName: string; + cardNumber: string; + expiryDate: string; + cvv: string; + shouldSaveCard: true; + }) => { + try { + setError(null); + setIsError(false); + setIsSubmitting(true); + const tokenizeBody: TokenizationParams = { + key: checkoutKey, + body: { + type: 'card', + number: data.cardNumber, + expiry_month: data.expiryDate.split('/')[0], + expiry_year: data.expiryDate.split('/')[1], + cvv: data.cvv, + name: data.fullName, + billing_address: undefined, + phone: undefined, + }, + }; + const tokenizedCard = await tokenize(tokenizeBody); + // incase of 3ds we will show the modal and wait for the user to complete the 3ds + // setShouldShow3dsModal(true); + cardTokenized(tokenizedCard as unknown as FrameCardTokenizedEvent); + } catch (error) { + setError(JSON.stringify(error)); + setIsError(true); + if (cardTokenizationFailed) { + cardTokenizationFailed(error as any); + } + } finally { + setIsSubmitting(false); + } + }; + + const getCardNumberSchema = () => + object().shape({ + fullName: string() + .min(2) + .matches( + /^[a-zA-Z]*[a-zA-Z-']+( [a-zA-Z-']+)*$/, + 'full name should have only alphabets', + ) + .required('Full name is required'), + cardNumber: string() + .matches(/^[0-9]+$/, 'Must be only digits') + .min(16) + .max(19) + .required("Card number can't be empty"), + expiryDate: string() + .matches(/^[0-9/]+$/, 'Must be only digits') + .cardExpiry() + .required('Expiry date is required'), + cvv: string() + .matches(/^[0-9]+$/, 'Must be only digits') + .min(3) + .max(4) + .required('CVV is required'), + shouldSaveCard: boolean().required('Should save card is required'), + }); + + return ( + + + Payment details + + Please enter your card details to continue + +
+ {({handleSubmit}) => { + return ( + <> + + + Name on card + + + + Card number + + + + + + + Expiry date + + + + + + CVV + + + + + + + + + By clicking proceeding I agree to the terms and conditions + + + + ); + }} + + { + setShouldShow3dsModal(false); + }}> + + {/* once 3ds is user will be redirect to our url will close the modal and user will move forward in wahed app */} + 3D secure authentication is required. Please complete the + authentication process to continue + + + { + setIsError(false); + }}> + + There was an error processing your payment. Please try again later{' '} + {error} + + +
+
+ ); +}; diff --git a/src/CardScreen/types/types.tsx b/src/CardScreen/types/types.tsx new file mode 100644 index 0000000..95f66d1 --- /dev/null +++ b/src/CardScreen/types/types.tsx @@ -0,0 +1,199 @@ +import { + ImageSourcePropType, + StyleProp, + TextInputProps, + TextStyle, + TouchableOpacityProps, + ViewStyle, +} from 'react-native'; + +export type Schemes = { + Visa: string; + Mastercard: string; + 'American Express': string; + Discover: string; + JCB: string; + 'Diners Club': string; + Maestro: string; + Mada: string; +}; + +export type CardNumberState = { + value: string; + icon: any; + valid: boolean; +}; + +export type FramesFieldProps = Omit; + +export type FramesCardFieldProps = { + showIcon?: boolean; +} & FramesFieldProps; + +type Validation = { + cardNumber: boolean; + expiryDate: boolean; + cvv: boolean; + card: boolean; +}; + +export type FramesState = { + cardNumber: string; + cardBin: CardBinChangedEvent; + cardIcon: ImageSourcePropType; + cardType: string; + expiryDate: string; + cvv: string; + cvvLength: number; + validation: Validation; +}; + +export type FramesDispatch = ({ + type, + payload, +}: { + type: string; + payload: any; +}) => void; + +export type FramesBillingAddress = { + addressLine1?: string; + addressLine2?: string; + zip?: string; + city?: string; + state?: string; + country?: string; +}; + +export type FramesCardholder = { + name?: string; + billingAddress?: FramesBillingAddress; + phone?: string; +}; + +export type FramesConfig = { + publicKey: string; + debug?: boolean; + cardholder?: FramesCardholder; +}; + +export type PaymentMethodChangeParams = { + isValid: boolean; + paymentMethod: string; +}; + +export type FrameValidationChangedParams = { + element: string; + isValid: boolean; + isEmpty: boolean; +}; + +export type FramesProps = { + style?: StyleProp; + children: any; + config: FramesConfig; + frameValidationChanged?: (e: FrameValidationChangedParams) => void; + paymentMethodChanged?: (e: PaymentMethodChangeParams) => void; + cardValidationChanged?: (e: FrameCardValidationChangedEvent) => void; + cardTokenized: (e: FrameCardTokenizedEvent) => void; + cardTokenizationFailed?: (e: FrameCardTokenizationFailedEvent) => void; + cardBinChanged?: (e: CardBinChangedEvent) => void; +} & ViewStyle; + +export type FrameCardValidationChangedEvent = { + isValid: boolean; + isElementValid: ValidationChange; +}; + +type ValidationChange = { + cardNumber: boolean; + expiryDate: boolean; + cvv: boolean; +}; + +export type FramesContextType = { + state: FramesState; + dispatch: FramesDispatch; + submitCard: () => void; +}; + +export type TokenizationParams = { + key: string; + body: TokenizationBody; +}; + +export type TokenizationBody = { + type: string; + number: string; + expiry_month: string; + expiry_year: string; + cvv: string; + name?: string; + billing_address?: GatewayBillingAddress; + phone?: Phone; +}; + +export type Phone = { + number?: string; +}; + +export type Scheme = + | 'Visa' + | 'Mastercard' + | 'AMERICAN EXPRESS' + | 'Diners Club International' + | 'Maestro' + | 'Discover' + | 'Mada'; + +export type CardType = 'Credit' | 'Debit' | 'Prepaid' | 'Charge'; +export type CardCategory = 'Consumer' | 'Commercial'; + +export type GatewayBillingAddress = { + address_line1?: string; + address_line2?: string; + city?: string; + state?: string; + zip?: string; + country?: string; +}; + +export type GatewayPhone = { + number?: string; +}; + +export type FrameCardTokenizedEvent = { + type: string; + token: string; + expires_on: string; + expiry_month: string; + expiry_year: string; + scheme?: Scheme; + last4: string; + bin: string; + card_type?: CardType; + card_category?: CardCategory; + issuer?: string; + issuer_country?: string; + product_id?: string; + product_type?: string; + billing_address?: GatewayBillingAddress; + phone?: GatewayPhone; + name?: string; +}; + +export type FrameCardTokenizationFailedEvent = { + error_codes: Array; + error_type: string; + request_id: string; +}; + +export type CardBinChangedEvent = { + bin: string; + scheme: string; +}; + +export type SubmitButtonProps = { + title: string; + textStyle?: TextStyle; +} & TouchableOpacityProps; diff --git a/src/ui/components/Button/Button.colors.ts b/src/ui/components/Button/Button.colors.ts new file mode 100644 index 0000000..2ae10db --- /dev/null +++ b/src/ui/components/Button/Button.colors.ts @@ -0,0 +1,224 @@ +import {Theme} from '../ThemeProvider'; +import {ButtonProps} from './types'; + +export const useColors = ({variant}: {variant: ButtonProps['variant']}) => { + const theme = Theme; + + switch (variant) { + case 'primaryInverted': { + return { + containerBackground: { + idle: theme.colors.white, + pressed: theme.colors.white, + loading: theme.colors.white, + disabled: theme.colors.lightGray5, + }, + text: { + idle: theme.colors.sky2, + pressed: theme.colors.sky2, + loading: theme.colors.sky2, + disabled: theme.colors.lightGray4, + }, + icon: { + disabled: theme.colors.lightGray4, + idle: theme.colors.sky2, + pressed: theme.colors.sky2, + loading: theme.colors.sky2, + }, + loader: { + idle: theme.colors.sky2, + disabled: theme.colors.lightGray4, + }, + loaderTrack: { + idle: theme.colors.lightGray6, + disabled: theme.colors.white, + }, + }; + } + case 'tertiary': { + return { + containerBackground: { + idle: theme.colors.primaryDark2, + pressed: theme.colors.primaryDark1, + loading: theme.colors.primary3, + disabled: theme.colors.lightGray5, + }, + text: { + idle: theme.colors.darkGray, + pressed: theme.colors.darkGray, + loading: theme.colors.darkGray, + disabled: theme.colors.lightGray4, + }, + icon: { + disabled: theme.colors.lightGray4, + idle: theme.colors.darkGray, + pressed: theme.colors.darkGray, + loading: theme.colors.darkGray, + }, + loader: { + idle: theme.colors.darkGray, + disabled: theme.colors.lightGray4, + }, + loaderTrack: { + idle: theme.colors.white, + disabled: theme.colors.white, + }, + }; + } + + case 'secondary': { + return { + containerBackground: { + idle: theme.colors.linkLight, + pressed: theme.colors.link5, + loading: theme.colors.link, + disabled: theme.colors.lightGray5, + }, + text: { + idle: theme.colors.link1, + pressed: theme.colors.link1, + loading: theme.colors.link1, + disabled: theme.colors.lightGray4, + }, + icon: { + disabled: theme.colors.lightGray4, + idle: theme.colors.link1, + pressed: theme.colors.link1, + loading: theme.colors.link1, + }, + loader: { + idle: theme.colors.white, + disabled: theme.colors.lightGray4, + }, + loaderTrack: { + idle: theme.colors.link1, + disabled: theme.colors.white, + }, + }; + } + + case 'minimal': { + return { + containerBackground: { + idle: theme.colors.linkLight, + pressed: theme.colors.link5, + loading: theme.colors.link, + disabled: theme.colors.lightGray5, + }, + text: { + idle: theme.colors.link1, + pressed: theme.colors.link1, + loading: theme.colors.link1, + disabled: theme.colors.lightGray4, + }, + icon: { + disabled: theme.colors.lightGray4, + idle: theme.colors.link1, + pressed: theme.colors.link1, + loading: theme.colors.link1, + }, + loader: { + idle: theme.colors.white, + disabled: theme.colors.lightGray4, + }, + loaderTrack: { + idle: theme.colors.link1, + disabled: theme.colors.white, + }, + }; + } + + case 'quaternary': { + return { + containerBackground: { + idle: theme.colors.white, + pressed: theme.colors.lightGray6, + loading: theme.colors.lightGray8, + disabled: theme.colors.lightGray5, + }, + text: { + idle: theme.colors.darkGray, + pressed: theme.colors.darkGray, + loading: theme.colors.darkGray, + disabled: theme.colors.lightGray4, + }, + icon: { + disabled: theme.colors.lightGray4, + idle: theme.colors.darkGray, + pressed: theme.colors.darkGray, + loading: theme.colors.darkGray, + }, + loader: { + idle: theme.colors.darkGray, + disabled: theme.colors.darkGray, + }, + loaderTrack: { + idle: theme.colors.white, + disabled: theme.colors.white, + }, + }; + } + + case 'quinary': { + return { + containerBackground: { + idle: theme.colors.white, + pressed: theme.colors.lightGray6, + loading: theme.colors.lightGray8, + disabled: theme.colors.lightGray5, + }, + text: { + idle: theme.colors.darkGray, + pressed: theme.colors.darkGray, + loading: theme.colors.darkGray, + disabled: theme.colors.lightGray4, + }, + icon: { + disabled: theme.colors.lightGray4, + idle: theme.colors.darkGray, + pressed: theme.colors.darkGray, + loading: theme.colors.darkGray, + }, + loader: { + idle: theme.colors.darkGray, + disabled: theme.colors.darkGray, + }, + loaderTrack: { + idle: theme.colors.white, + disabled: theme.colors.white, + }, + }; + } + + default: { + return { + containerBackground: { + idle: theme.colors.sky6, + pressed: theme.colors.sky5, + loading: theme.colors.sky4, + disabled: theme.colors.lightGray5, + }, + text: { + idle: theme.colors.sky1, + pressed: theme.colors.sky1, + loading: theme.colors.sky1, + disabled: theme.colors.lightGray4, + }, + icon: { + disabled: theme.colors.lightGray4, + idle: theme.colors.sky1, + pressed: theme.colors.sky1, + loading: theme.colors.sky1, + }, + loader: { + idle: theme.colors.sky1, + disabled: theme.colors.lightGray4, + }, + loaderTrack: { + idle: theme.colors.white, + disabled: theme.colors.white, + }, + }; + } + } +}; diff --git a/src/ui/components/Button/Button.config.ts b/src/ui/components/Button/Button.config.ts new file mode 100644 index 0000000..5114202 --- /dev/null +++ b/src/ui/components/Button/Button.config.ts @@ -0,0 +1,60 @@ +import {Theme} from '../ThemeProvider'; +import {BtnConfiguration, ButtonProps} from './types'; + +export const useConfiguration = ( + variant: ButtonProps['variant'], + size: ButtonProps['size'], + hasIcon?: boolean, +): BtnConfiguration => { + const theme = Theme; + if (variant === 'minimal') { + return size === 'small' + ? { + borderRadius: 24, + iconSize: 'xxxxs', + fontSize: 14, + verticalPadding: 2, + horizontalPadding: theme.spacing.xxs, + loaderSize: 12, + borderColor: undefined, + borderWidth: 0, + } + : { + borderRadius: 32, + iconSize: 'xxs', + fontSize: 18, + verticalPadding: 5, + horizontalPadding: theme.spacing.xs, + loaderSize: 16, + borderColor: undefined, + borderWidth: 0, + }; + } + + if (variant === 'quinary') { + return { + lineHeight: 24, + borderRadius: size === 'large' ? 48 : 40, + iconSize: 'xxs', + fontSize: 18, + verticalPadding: size === 'large' ? 12 : hasIcon ? 8 : 10, + horizontalPadding: size === 'large' ? theme.spacing.m : theme.spacing.xs, + loaderSize: 24, + borderColor: theme.colors.lightGray5, + borderWidth: 1, + }; + } + + return { + lineHeight: size === 'small' ? 22 : 24, + borderRadius: size === 'large' ? 48 : 40, + iconSize: size === 'small' ? 'xxxs' : 'xxs', + fontSize: size === 'small' ? 16 : 18, + verticalPadding: + size === 'small' ? (hasIcon ? 8 : 10) : size === 'large' ? 12 : 8, + horizontalPadding: size === 'large' ? theme.spacing.m : theme.spacing.xs, + loaderSize: size === 'small' ? 18 : 24, + borderColor: undefined, + borderWidth: 0, + }; +}; diff --git a/src/ui/components/Button/Button.styles.ts b/src/ui/components/Button/Button.styles.ts new file mode 100644 index 0000000..d795a98 --- /dev/null +++ b/src/ui/components/Button/Button.styles.ts @@ -0,0 +1,117 @@ +import {Animated, ViewStyle} from 'react-native'; +import {useAnimation} from 'react-native-animation-hooks'; +import {Theme} from '../ThemeProvider'; +import {getFont} from '../../utils/getFont'; +import {useColors} from './Button.colors'; +import {BtnUseStylesProps} from './types'; + +export const useStyles = ({ + isPressed, + isDisabled, + isLoading, + variant, + config, +}: BtnUseStylesProps) => { + const colors = useColors({ + variant, + }); + const theme = Theme; + + const animation = useAnimation({ + type: 'timing', + initialValue: 0, + toValue: isPressed ? 1 : 0, + duration: 300, + useNativeDriver: false, + }); + + const loaderVisibilityAnimation = useAnimation({ + type: 'timing', + initialValue: 0, + toValue: isLoading ? 1 : 0, + duration: 150, + useNativeDriver: false, + }); + + const font = getFont({ + languageCode: theme.languageCode, + isRtl: theme.direction === 'rtl', + }); + + return { + container: { + borderColor: config.borderColor, + borderWidth: config.borderWidth, + borderRadius: config.borderRadius, + backgroundColor: animation.interpolate({ + inputRange: [0, 1], + outputRange: [ + isDisabled + ? colors.containerBackground.disabled + : isLoading + ? colors.containerBackground.loading + : colors.containerBackground.idle, + colors.containerBackground.pressed, + ], + }), + position: 'relative', + } as Animated.WithAnimatedObject, + content: { + paddingVertical: config.verticalPadding, + paddingHorizontal: config.horizontalPadding, + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + opacity: loaderVisibilityAnimation.interpolate({ + inputRange: [0, 1], + outputRange: [1, 0], + }), + } as Animated.WithAnimatedObject, + buttonText: { + fontFamily: font.medium, + fontSize: config.fontSize, + lineHeight: config.lineHeight, + color: animation.interpolate({ + inputRange: [0, 1], + outputRange: [ + isDisabled + ? colors.text.disabled + : isLoading + ? colors.text.loading + : colors.text.idle, + colors.text.pressed, + ], + }), + }, + icon: { + color: animation.interpolate({ + inputRange: [0, 1], + outputRange: [ + isDisabled + ? colors.icon.disabled + : isLoading + ? colors.icon.loading + : colors.icon.idle, + colors.icon.pressed, + ], + }), + }, + loaderWrapper: { + width: '100%', + height: '100%', + alignItems: 'center', + justifyContent: 'center', + opacity: loaderVisibilityAnimation, + position: 'absolute', + top: 0, + left: 0, + flexDirection: 'row', + } as Animated.WithAnimatedObject, + loader: { + color: isDisabled ? colors.loader.disabled : colors.loader.idle, + trackColor: isDisabled + ? colors.loaderTrack.disabled + : colors.loaderTrack.idle, + }, + }; +}; diff --git a/src/ui/components/Button/Button.tsx b/src/ui/components/Button/Button.tsx new file mode 100644 index 0000000..84555a9 --- /dev/null +++ b/src/ui/components/Button/Button.tsx @@ -0,0 +1,95 @@ +import React, {FC, PropsWithChildren, useMemo, useState} from 'react'; +import { + Animated, + TouchableHighlight, + View, + ActivityIndicator, +} from 'react-native'; +import {withAnimation} from '../../hocs/withAnimation'; +import {useMargin, useTestID} from '../../hooks'; +import {useConfiguration} from './Button.config'; +import {useStyles} from './Button.styles'; +import {ButtonProps} from './types'; + +export const Button: FC> = ({ + size = 'large', + variant = 'primary', + iconPosition = 'end', + testID, + accessibility, + onPress, + children, + isDisabled, + isLoading, + icon: Icon, + ...rest +}) => { + const {getTestID} = useTestID({testID}); + const [isPressed, setIsPressed] = useState(false); + + const config = useConfiguration(variant, size, !!Icon); + const margin = useMargin(rest); + const styles = useStyles({ + isPressed, + variant, + isDisabled, + isLoading, + hasIcon: Icon ? true : false, + config, + }); + + const icon = useMemo(() => { + if (!Icon) { + return null; + } + + const AnimatedIcon = withAnimation(Icon); + + return ( + !!Icon && ( + + + + ) + ); + }, [Icon, config.iconSize, iconPosition, size, styles.icon.color]); + + return ( + setIsPressed(false)} + onShowUnderlay={() => setIsPressed(true)}> + + + {iconPosition === 'start' && icon} + + {children} + + {iconPosition === 'end' && icon} + + + + + + + ); +}; diff --git a/src/ui/components/Button/index.ts b/src/ui/components/Button/index.ts new file mode 100644 index 0000000..9d5f3b2 --- /dev/null +++ b/src/ui/components/Button/index.ts @@ -0,0 +1,2 @@ +export * from './Button'; +export * from './types'; diff --git a/src/ui/components/Button/types.ts b/src/ui/components/Button/types.ts new file mode 100644 index 0000000..01a3399 --- /dev/null +++ b/src/ui/components/Button/types.ts @@ -0,0 +1,87 @@ +import {FC} from 'react'; +import {UseMarginProps, UseTestIDProps} from '../../hooks'; +import {AccessibilityProps} from '../../types/AccessibilityProps'; + +type ButtonSizes = 'large' | 'medium' | 'small'; + +export type ButtonProps = { + /** + * Web only: if set, will render the a tag instead of button + */ + href?: string; + + /** + * Handles button press + */ + onPress?: () => void; + + /** + * Button variant from design system + * + * @default 'large' + */ + variant?: + | 'primary' + | 'secondary' + | 'tertiary' + | 'primaryInverted' + | 'minimal' + | 'quaternary' + | 'quinary'; + + /** + * If set to true, will put the component in disabled state + */ + isDisabled?: boolean; + + /** + * If set to true, will disable button interaction and show loader + */ + isLoading?: boolean; + + /** + * Icon + */ + icon?: FC; + + /** + * Button size + * + * @default 'large' + */ + size?: ButtonSizes; + + /** + * Icon position If set, will render icon at the start or end of a button + * + * @default 'end' + */ + iconPosition?: 'start' | 'end'; + + /** + * If set, will add accessibility properties button + */ + accessibility?: AccessibilityProps; +} & UseMarginProps & + UseTestIDProps; + +export type BtnConfiguration = { + borderRadius: number; + borderColor?: string; + borderWidth: number; + iconSize: string; + fontSize: number; + verticalPadding: number; + horizontalPadding: number; + loaderSize: number; + lineHeight?: number; +}; + +export type BtnUseStylesProps = { + isPressed: boolean; + isDisabled?: boolean; + isLoading?: boolean; + variant: ButtonProps['variant']; + hasIcon: boolean; + config: BtnConfiguration; +}; diff --git a/src/ui/components/Checkbox/Checkbox.hooks.ts b/src/ui/components/Checkbox/Checkbox.hooks.ts new file mode 100644 index 0000000..57e01a8 --- /dev/null +++ b/src/ui/components/Checkbox/Checkbox.hooks.ts @@ -0,0 +1,33 @@ +import {useCallback, useEffect, useState} from 'react'; + +type UseCheckableProps = { + // Initial checked value + isChecked?: boolean; + onChange?(isChecked: boolean): void; +}; + +export const useCheckable = (props: UseCheckableProps) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const {onChange: _onChange = () => null} = props; + const [isChecked, setIsChecked] = useState(props.isChecked); + + useEffect(() => { + setIsChecked(props.isChecked); + }, [props.isChecked]); + + const onChange = useCallback(() => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const newIsChecked = !isChecked; + + if (props.isChecked === undefined) { + setIsChecked(newIsChecked); + } + + _onChange(newIsChecked); + }, [_onChange, isChecked, props.isChecked]); + + return { + isChecked, + onChange, + }; +}; diff --git a/src/ui/components/Checkbox/Checkbox.styles.ts b/src/ui/components/Checkbox/Checkbox.styles.ts new file mode 100644 index 0000000..af9ec2b --- /dev/null +++ b/src/ui/components/Checkbox/Checkbox.styles.ts @@ -0,0 +1,70 @@ +import {StyleProp, ViewStyle} from 'react-native'; +import {useAnimation} from 'react-native-animation-hooks'; + +import {Theme} from '../ThemeProvider'; + +type UseStylesProps = { + isChecked?: boolean; + isDisabled?: boolean; +}; + +export const useStyles = (props: UseStylesProps) => { + const theme = Theme; + + const animatedRadioSize = useAnimation({ + type: 'timing', + initialValue: 0, + toValue: props.isChecked ? 1 : 0, + duration: 150, + useNativeDriver: false, + }); + + return { + container: { + flexDirection: 'row', + alignItems: 'center', + } as StyleProp, + checkContainer: { + width: 20, + height: 20, + position: 'relative', + alignItems: 'center', + justifyContent: 'center', + } as StyleProp, + radioContainer: { + width: 24, + height: 24, + alignItems: 'center', + justifyContent: 'center', + } as StyleProp, + radio: { + width: 20, + height: 20, + borderRadius: 10, + borderWidth: 2, + borderColor: props.isDisabled + ? theme.colors.lightGray4 + : props.isChecked + ? theme.colors.linkDark + : theme.colors.lightGray4, + alignItems: 'center', + justifyContent: 'center', + } as StyleProp, + radioCheck: { + width: animatedRadioSize.interpolate({ + inputRange: [0, 1], + outputRange: [0, 10], + }), + height: animatedRadioSize.interpolate({ + inputRange: [0, 1], + outputRange: [0, 10], + }), + borderRadius: 10, + backgroundColor: props.isDisabled + ? theme.colors.lightGray4 + : props.isChecked + ? theme.colors.linkDark + : theme.colors.lightGray4, + }, + }; +}; diff --git a/src/ui/components/Checkbox/Checkbox.tsx b/src/ui/components/Checkbox/Checkbox.tsx new file mode 100644 index 0000000..9b260b5 --- /dev/null +++ b/src/ui/components/Checkbox/Checkbox.tsx @@ -0,0 +1,84 @@ +import React, {FC} from 'react'; +import {Animated, Pressable, View} from 'react-native'; + +import {IconCheckbox, IconCheckboxOutline} from '../Icons'; +import {useMargin, useTestID} from '../../hooks'; +import {Text} from '../Text'; +import {useCheckable} from './Checkbox.hooks'; +import {useStyles} from './Checkbox.styles'; +import {CheckboxProps} from './props'; +import {Theme} from '../ThemeProvider'; + +export const Checkbox: FC = ({ + isChecked, + type = 'checkbox', + isDisabled, + testID, + label, + onChange = () => null, + ...rest +}) => { + const theme = Theme; + const styles = useStyles({ + isChecked, + isDisabled, + }); + const margin = useMargin(rest); + + const {getTestID} = useTestID({testID}); + + const checkboxTestId = getTestID('checkbox'); + const checkable = useCheckable({ + isChecked, + onChange, + }); + + return ( + + + + + {type === 'checkbox' ? ( + checkable.isChecked ? ( + + ) : ( + + ) + ) : ( + + + + + + )} + + + + {label && ( + + + {label} + + + )} + + ); +}; diff --git a/src/ui/components/Checkbox/index.ts b/src/ui/components/Checkbox/index.ts new file mode 100644 index 0000000..b27683d --- /dev/null +++ b/src/ui/components/Checkbox/index.ts @@ -0,0 +1,2 @@ +export * from './Checkbox'; +export * from './props'; diff --git a/src/ui/components/Checkbox/props.ts b/src/ui/components/Checkbox/props.ts new file mode 100644 index 0000000..27947e3 --- /dev/null +++ b/src/ui/components/Checkbox/props.ts @@ -0,0 +1,33 @@ +import { ReactElement } from 'react'; +import { UseMarginProps, UseTestIDProps } from '../../hooks'; + +export type CheckboxProps = { + /* + * If true, will check the checkbox. If set, the component will become + * controlled component + */ + isChecked?: boolean; + + /* + * If true, will disable the checkbox. User won't be able to interact with it + */ + isDisabled?: boolean; + + /* + * Label for the checkbox. Can be string or JSX markup + */ + label?: ReactElement | string; + + /** + * Checkbox type + * + * @default 'checkbox' + */ + type?: 'checkbox' | 'radio'; + + /* + * Handles change of the checkbox state + */ + onChange(isChecked?: boolean): void; +} & UseMarginProps & + UseTestIDProps; diff --git a/src/ui/components/Form/Form.tsx b/src/ui/components/Form/Form.tsx new file mode 100644 index 0000000..43808ba --- /dev/null +++ b/src/ui/components/Form/Form.tsx @@ -0,0 +1,85 @@ +import {ReactNode, useEffect} from 'react'; +import { + CriteriaMode, + DefaultValues, + FieldErrors, + FieldValues, + FormProvider, + KeepStateOptions, + Resolver, + SubmitErrorHandler, + useForm, + UseFormReset, + UseFormReturn, + ValidationMode, +} from 'react-hook-form'; +import {StyleProp, View, ViewStyle} from 'react-native'; +import {yupResolver} from '@hookform/resolvers/yup'; +import * as yup from 'yup'; + +export type FormProps = { + children?: ((props: UseFormReturn) => ReactNode) | ReactNode; + values?: T; + defaultValues?: DefaultValues; + validationSchema?: yup.ObjectSchema; + validationMode?: keyof ValidationMode; + shouldUnregister?: boolean; + onSubmitOnChange?: (data: T, reset: UseFormReset) => void; + resetOptions?: KeepStateOptions; + errors?: FieldErrors; + onError?: SubmitErrorHandler; + criteriaMode?: CriteriaMode; + style?: StyleProp; +}; + +export const Form = ({ + children, + values, + defaultValues, + validationSchema, + validationMode = 'onSubmit', + shouldUnregister, + onSubmitOnChange, + resetOptions, + criteriaMode, + errors, + onError, + style, +}: FormProps) => { + const resolver = validationSchema + ? (yupResolver(validationSchema) as unknown as Resolver) + : undefined; + + const methods = useForm({ + resolver, + values, + defaultValues, + errors, + resetOptions, + shouldUnregister, + criteriaMode, + mode: validationMode, + }); + const {handleSubmit, reset, watch, formState} = methods; + + useEffect(() => { + if (onSubmitOnChange && formState.isValid) { + handleSubmit((data: T) => onSubmitOnChange(data, reset), onError)(); + } + }, [ + formState.isValid, + handleSubmit, + onError, + onSubmitOnChange, + reset, + watch, + ]); + + return ( + + + {typeof children === 'function' ? children({...methods}) : children} + + + ); +}; diff --git a/src/ui/components/Form/FormCheckbox.tsx b/src/ui/components/Form/FormCheckbox.tsx new file mode 100644 index 0000000..98ba364 --- /dev/null +++ b/src/ui/components/Form/FormCheckbox.tsx @@ -0,0 +1,47 @@ +import {FC, useEffect} from 'react'; +import {FieldError, useController, useFormContext} from 'react-hook-form'; + +import {Checkbox, CheckboxProps} from '../Checkbox'; + +export type FormCheckboxProps = { + name: string; + isRequired?: boolean; + errorObj?: FieldError; + testID?: string; +} & Omit; + +export const FormCheckbox: FC = ({ + name, + label, + isDisabled, + errorObj, + testID, + ...rest +}) => { + const {control, setError} = useFormContext(); + + const { + field: {value: checked, onChange}, + } = useController({ + name, + control, + disabled: isDisabled, + }); + + useEffect(() => { + if (errorObj?.message) { + setError(name, errorObj); + } + }, [name, setError, errorObj]); + + return ( + + ); +}; diff --git a/src/ui/components/Form/FormTextInput.tsx b/src/ui/components/Form/FormTextInput.tsx new file mode 100644 index 0000000..0daf8b5 --- /dev/null +++ b/src/ui/components/Form/FormTextInput.tsx @@ -0,0 +1,71 @@ +import {forwardRef, useEffect} from 'react'; +import { + FieldError, + RegisterOptions, + useController, + useFormContext, +} from 'react-hook-form'; +import {InputText, InputTextProps, InputTextRef} from '../InputText'; + +export type FormTextInputProps = { + name: string; + errorObj?: FieldError; + onChange?: RegisterOptions['onChange']; + isRequired?: boolean; + hideValidatedIcon?: boolean; +} & Omit; + +export const FormTextInput = forwardRef( + ( + { + name, + errorObj, + label, + placeholder, + onChange, + isRequired, + isReadOnly, + marginTop = 'm', + hideValidatedIcon, + ...rest + }, + ref, + ) => { + const {getFieldState, setError, control} = useFormContext(); + + const { + field: {value, onChange: onChangeField, onBlur}, + fieldState: {error: fieldError}, + } = useController({ + name, + control, + }); + + const isValid = !getFieldState(name).invalid && Boolean(value); + + useEffect(() => { + if (errorObj?.message) { + setError(name, errorObj); + } + }, [name, setError, errorObj]); + + return ( + + ); + }, +); diff --git a/src/ui/components/Form/index.ts b/src/ui/components/Form/index.ts new file mode 100644 index 0000000..6c84d4d --- /dev/null +++ b/src/ui/components/Form/index.ts @@ -0,0 +1,2 @@ +export * from './Form'; +export * from './FormTextInput'; diff --git a/src/ui/components/Icons/IconAbout.tsx b/src/ui/components/Icons/IconAbout.tsx new file mode 100644 index 0000000..60f30be --- /dev/null +++ b/src/ui/components/Icons/IconAbout.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; +// eslint-disable-next-line import/no-extraneous-dependencies +import Svg, {SvgProps, Path} from 'react-native-svg'; + +export const IconAbout = (props: SvgProps) => ( + + + +); diff --git a/src/ui/components/Icons/IconCheck.tsx b/src/ui/components/Icons/IconCheck.tsx new file mode 100644 index 0000000..22556dd --- /dev/null +++ b/src/ui/components/Icons/IconCheck.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; +// eslint-disable-next-line import/no-extraneous-dependencies +import Svg, {Path, SvgProps} from 'react-native-svg'; + +export const IconCheck = (props: SvgProps) => ( + + + +); diff --git a/src/ui/components/Icons/IconCheckbox.tsx b/src/ui/components/Icons/IconCheckbox.tsx new file mode 100644 index 0000000..95529e8 --- /dev/null +++ b/src/ui/components/Icons/IconCheckbox.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; +// eslint-disable-next-line import/no-extraneous-dependencies +import Svg, {Path, SvgProps} from 'react-native-svg'; + +export const IconCheckbox = (props: SvgProps) => ( + + + +); diff --git a/src/ui/components/Icons/IconCheckboxOutline.tsx b/src/ui/components/Icons/IconCheckboxOutline.tsx new file mode 100644 index 0000000..e247075 --- /dev/null +++ b/src/ui/components/Icons/IconCheckboxOutline.tsx @@ -0,0 +1,15 @@ +import * as React from 'react'; +// eslint-disable-next-line import/no-extraneous-dependencies +import Svg, {Path, SvgProps} from 'react-native-svg'; + +export const IconCheckboxOutline = (props: SvgProps) => ( + + + +); diff --git a/src/ui/components/Icons/IconClose.tsx b/src/ui/components/Icons/IconClose.tsx new file mode 100644 index 0000000..61e630e --- /dev/null +++ b/src/ui/components/Icons/IconClose.tsx @@ -0,0 +1,12 @@ +import * as React from 'react'; +// eslint-disable-next-line import/no-extraneous-dependencies +import Svg, {Path, SvgProps} from 'react-native-svg'; + +export const IconClose = (props: SvgProps) => ( + + + +); diff --git a/src/ui/components/Icons/index.tsx b/src/ui/components/Icons/index.tsx new file mode 100644 index 0000000..b8d59c3 --- /dev/null +++ b/src/ui/components/Icons/index.tsx @@ -0,0 +1,5 @@ +export * from './IconCheck'; +export * from './IconClose'; +export * from './IconAbout'; +export * from './IconCheckbox'; +export * from './IconCheckboxOutline'; diff --git a/src/ui/components/InputText/InputText.styles.ts b/src/ui/components/InputText/InputText.styles.ts new file mode 100644 index 0000000..94b2fbe --- /dev/null +++ b/src/ui/components/InputText/InputText.styles.ts @@ -0,0 +1,126 @@ +import {Animated, StyleProp, TextStyle, ViewStyle} from 'react-native'; +import {useAnimation} from 'react-native-animation-hooks'; + +import {Theme} from '../ThemeProvider'; +import {getFont} from '../../utils/getFont'; + +type UseStylesProps = { + isFocused: boolean; + isFilled: boolean; + isDisabled: boolean; + isReadOnly: boolean; + hasLabel: boolean; +}; +export const useStyles = ({ + isFocused, + isFilled, + isDisabled, + isReadOnly, + hasLabel, +}: UseStylesProps) => { + const theme = Theme; + const isDisabledStyled = isDisabled || isReadOnly; + const isFocusedOrFilled = isFilled || isFocused; + const focusedOrFilledAnimation = useAnimation({ + type: 'timing', + initialValue: 0, + toValue: isFocusedOrFilled ? 1 : 0, + duration: 150, + useNativeDriver: false, + }); + const focusedAnimation = useAnimation({ + type: 'timing', + initialValue: 0, + toValue: isFocused ? 1 : 0, + duration: 150, + useNativeDriver: false, + }); + + const hasLabelOrNotFilledNorFocused = hasLabel || (!isFilled && !isFocused); + + const font = getFont({ + languageCode: theme.languageCode, + isRtl: theme.direction === 'rtl', + }); + + const backgroundColors = { + focused: theme.colors.lightGray8, + filled: theme.colors.lightGray6, + disabled: theme.colors.lightGray7, + }; + + return { + wrapper: {} as StyleProp, + prefixIconContainer: { + marginEnd: theme.spacing.xxs, + }, + container: { + alignItems: 'center', + borderRadius: 12, + backgroundColor: focusedAnimation.interpolate({ + inputRange: [0, 1], + outputRange: [ + isFocused + ? backgroundColors.focused + : isDisabledStyled + ? backgroundColors.disabled + : backgroundColors.filled, + backgroundColors.focused, + ], + }), + overflow: 'hidden', + flexDirection: 'row', + paddingHorizontal: theme.spacing.s, + height: 60, + } as Animated.WithAnimatedObject, + label: { + position: 'absolute', + top: focusedOrFilledAnimation.interpolate({ + inputRange: [0, 1], + outputRange: [18, 8], + }), + fontSize: focusedOrFilledAnimation.interpolate({ + inputRange: [0, 1], + outputRange: [20, 14], + }), + lineHeight: focusedOrFilledAnimation.interpolate({ + inputRange: [0, 1], + outputRange: [24, 16], + }), + color: isDisabledStyled + ? theme.colors.lightGray4 + : theme.colors.lightGray3, + fontFamily: font.regular, + flex: 1, + width: '100%', + textAlign: 'left', + } as Animated.WithAnimatedObject, + inputContainer: { + flexDirection: 'row', + alignItems: 'flex-end', + position: 'relative', + flexShrink: 1, + overflow: 'hidden', + } as Animated.WithAnimatedObject, + input: { + paddingTop: hasLabelOrNotFilledNorFocused + ? theme.spacing.m + : theme.spacing.xxs, + paddingBottom: theme.spacing.xxxs, + backgroundColor: 'transparent', + paddingHorizontal: 0, + fontSize: 20, + lineHeight: 24, + flexShrink: 1, + flexGrow: 1, + color: isDisabledStyled ? theme.colors.lightGray4 : theme.colors.darkGray, + fontFamily: font.regular, + height: 60, + }, + iconContainer: { + flexDirection: 'row', + marginStart: theme.spacing.xxxs, + flexShrink: 0, + } as StyleProp, + }; +}; diff --git a/src/ui/components/InputText/InputText.tsx b/src/ui/components/InputText/InputText.tsx new file mode 100644 index 0000000..347373d --- /dev/null +++ b/src/ui/components/InputText/InputText.tsx @@ -0,0 +1,203 @@ +import React, { + forwardRef, + useEffect, + useImperativeHandle, + useMemo, + useRef, + useState, +} from 'react'; +import { + Animated, + KeyboardTypeOptions, + NativeSyntheticEvent, + Platform, + TextInput, + TextInputFocusEventData, + TouchableOpacity, + View, +} from 'react-native'; + +import {IconAbout, IconCheck, IconClose} from '../Icons'; +import {useMargin, useTestID} from '../../hooks'; +import {Theme} from '../ThemeProvider'; +import {Text} from '../Text'; +import {useStyles} from './InputText.styles'; +import {InputTextProps, InputTextRef} from './props'; + +export const InputText = forwardRef( + ( + { + type = 'text', + testID, + label, + placeholder, + value: valueProps, + defaultValue: defaultValueProps, + onChange = () => null, + onTextClear, + isDisabled = false, + isValidated = false, + isReadOnly = false, + autoFocus = false, + onFocus, + onBlur, + error, + maxLength, + prefixIcon, + ...restProps + }, + ref, + ) => { + const theme = Theme; + const margin = useMargin(restProps); + const {getTestID} = useTestID({testID}); + const [isFocused, setIsFocused] = useState(false); + const [value, setValue] = useState(valueProps || ''); + const [defaultValue, setDefaultValue] = useState(defaultValueProps); + const PrefixIcon = prefixIcon; + const styles = useStyles({ + isFocused, + isFilled: !!value, + isDisabled, + isReadOnly, + hasLabel: !!label, + }); + + const inputRef = useRef(null); + useImperativeHandle(ref, () => ({ + focus: () => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, + })); + + useEffect(() => { + setValue(valueProps || defaultValueProps || ''); + setDefaultValue(defaultValueProps); + }, [valueProps, defaultValueProps]); + + const handleOnFocus = ( + e: NativeSyntheticEvent, + ) => { + if (onFocus) { + onFocus(e); + } + setIsFocused(true); + }; + const handleOnBlur = (e: NativeSyntheticEvent) => { + if (onBlur) { + onBlur(e); + } + setIsFocused(false); + }; + const onChangeText = (value: string) => { + setValue(value); + setDefaultValue(value); + onChange(value); + }; + + const handleOnClearText = () => { + if (onTextClear) { + onTextClear(); + } + + setValue(''); + setDefaultValue(defaultValueProps); + onChange(''); + }; + + const keyboardType: KeyboardTypeOptions = useMemo(() => { + switch (type) { + case 'email': + return 'email-address'; + case 'number': + return 'numeric'; + default: + return 'default'; + } + }, [type]); + + const isAndroidRtl = () => { + return Platform.OS === 'android' && theme.direction === 'rtl'; + }; + + return ( + + + {!!PrefixIcon && ( + + + + )} + + + {isFocused || !!value ? label : placeholder || label} + + {/* unable to show ellipsis as ellipsizeMode is not supported by TextInput */} + + + + {isValidated && !error && ( + + )} + {!!error && } + {!!value && !isDisabled && !isReadOnly && ( + + + + )} + + + {!!error && ( + + {error} + + )} + + ); + }, +); diff --git a/src/ui/components/InputText/index.ts b/src/ui/components/InputText/index.ts new file mode 100644 index 0000000..fcff07c --- /dev/null +++ b/src/ui/components/InputText/index.ts @@ -0,0 +1,2 @@ +export * from './InputText'; +export * from './props'; diff --git a/src/ui/components/InputText/props.ts b/src/ui/components/InputText/props.ts new file mode 100644 index 0000000..ed28ba0 --- /dev/null +++ b/src/ui/components/InputText/props.ts @@ -0,0 +1,99 @@ +import {FC, RefAttributes} from 'react'; + +import {UseMarginProps, UseTestIDProps} from '../../hooks'; + +export type InputTextRef = { + /** + * When called, will focus inputText component + */ + focus: () => void; +}; + +export type InputTextProps = { + /** + * Input type + */ + type?: 'text' | 'email' | 'number'; + + /** + * Input label + */ + label?: string; + + /** + * Input value. + * If set, component will become a controlled component + */ + value?: string; + + /** + * Input default value. + * Provides an initial value that is returned even when isDisabled is true + * the value will change when the user starts typing. + * see: https://reactnative.dev/docs/textinput#defaultvalue + */ + defaultValue?: string; + + /** + * If set, will display placeholder instead of label if + * component is empty + */ + placeholder?: string; + + /** + * If set to true, will disable input + */ + isDisabled?: boolean; + + /** + * If set to true, will show validated icon + */ + isValidated?: boolean; + + /** + * marks the field as read only + */ + isReadOnly?: boolean; + + /** + * Error message. If set, will render input in error state + */ + error?: string; + + /** + * If set to true, will set maximum length + */ + maxLength?: number; + + /** + * If set to true, will automatically focus input element + */ + autoFocus?: boolean; + + /** + * Returns the React or Native Focus event + */ + onFocus?: (e: any) => void; + + /** + * Returns the React or Native Blur event + */ + onBlur?: (e: any) => void; + + /** + * Prefix icon to be shown on the start of the component + */ + prefixIcon?: FC; + + /** + * Callback function called when clear icon is clicked + */ + onTextClear?: () => void; + + /** + * Handles input text change + */ + onChange?: (value: string) => void; +} & UseMarginProps & + UseTestIDProps & + RefAttributes; diff --git a/src/ui/components/Knob/Knob.colors.ts b/src/ui/components/Knob/Knob.colors.ts new file mode 100644 index 0000000..32f80a3 --- /dev/null +++ b/src/ui/components/Knob/Knob.colors.ts @@ -0,0 +1,157 @@ +import {Theme} from '../ThemeProvider'; +import {KnobProps} from './props'; + +export const useColors = ({variant}: {variant: KnobProps['variant']}) => { + const theme = Theme; + + switch (variant) { + case 'tertiary': { + return { + containerBackground: { + idle: theme.colors.white, + pressed: theme.colors.lightGray6, + loading: theme.colors.white, + disabled: theme.colors.lightGray5, + }, + icon: { + disabled: theme.colors.lightGray4, + idle: theme.colors.darkGray, + pressed: theme.colors.darkGray, + loading: theme.colors.darkGray, + }, + loader: { + idle: theme.colors.darkGray, + disabled: theme.colors.lightGray4, + }, + loaderTrack: { + idle: theme.colors.lightGray5, + disabled: theme.colors.white, + }, + }; + } + case 'secondary': { + return { + containerBackground: { + idle: theme.colors.lightGray8, + pressed: theme.colors.lightGray5, + loading: theme.colors.lightGray4, + disabled: theme.colors.lightGray5, + }, + icon: { + disabled: theme.colors.lightGray4, + idle: theme.colors.darkGray, + pressed: theme.colors.darkGray, + loading: theme.colors.darkGray, + }, + loader: { + idle: theme.colors.darkGray, + disabled: theme.colors.lightGray4, + }, + loaderTrack: { + idle: theme.colors.white, + disabled: theme.colors.white, + }, + }; + } + case 'quaternary': { + return { + containerBackground: { + idle: theme.colors.primaryDark2, + pressed: theme.colors.primaryDark1, + loading: theme.colors.primary3, + disabled: theme.colors.lightGray5, + }, + icon: { + disabled: theme.colors.lightGray4, + idle: theme.colors.darkGray, + pressed: theme.colors.darkGray, + loading: theme.colors.primary7, + }, + loader: { + idle: theme.colors.sky1, + disabled: theme.colors.lightGray4, + }, + loaderTrack: { + idle: theme.colors.white, + disabled: theme.colors.white, + }, + }; + } + case 'quinary': { + return { + containerBackground: { + idle: theme.colors.linkLight, + pressed: theme.colors.link5, + loading: theme.colors.link, + disabled: theme.colors.lightGray5, + }, + icon: { + disabled: theme.colors.lightGray4, + idle: theme.colors.link1, + pressed: theme.colors.link1, + loading: theme.colors.link1, + }, + loader: { + idle: theme.colors.link1, + disabled: theme.colors.lightGray4, + }, + loaderTrack: { + idle: theme.colors.white, + disabled: theme.colors.white, + }, + }; + } + case 'whatsApp': { + const whatsAppDefault = '#60D66A'; + const whatsAppPressed = '#57C360'; + const whatsAppLoading = '#44984B'; + + return { + containerBackground: { + idle: whatsAppDefault, + pressed: whatsAppPressed, + loading: whatsAppLoading, + disabled: theme.colors.lightGray5, + }, + icon: { + disabled: theme.colors.lightGray4, + idle: theme.colors.white, + pressed: theme.colors.white, + loading: theme.colors.white, + }, + loader: { + idle: theme.colors.link1, + disabled: theme.colors.lightGray4, + }, + loaderTrack: { + idle: theme.colors.white, + disabled: theme.colors.white, + }, + }; + } + case 'primary': + default: + return { + containerBackground: { + idle: theme.colors.sky6, + pressed: theme.colors.sky5, + loading: theme.colors.sky4, + disabled: theme.colors.lightGray5, + }, + icon: { + disabled: theme.colors.lightGray4, + idle: theme.colors.sky1, + pressed: theme.colors.sky1, + loading: theme.colors.sky1, + }, + loader: { + idle: theme.colors.sky1, + disabled: theme.colors.lightGray4, + }, + loaderTrack: { + idle: theme.colors.white, + disabled: theme.colors.white, + }, + }; + } +}; diff --git a/src/ui/components/Knob/Knob.styles.ts b/src/ui/components/Knob/Knob.styles.ts new file mode 100644 index 0000000..dfe0202 --- /dev/null +++ b/src/ui/components/Knob/Knob.styles.ts @@ -0,0 +1,115 @@ +import {useEffect, useRef} from 'react'; +import {Animated, Easing, ViewStyle} from 'react-native'; +import {useAnimation} from 'react-native-animation-hooks'; + +import {useColors} from './Knob.colors'; +import {KnobProps} from './props'; + +type UseStylesProps = { + isPressed: boolean; + isDisabled?: boolean; + isLoading?: boolean; + size: KnobProps['size']; + variant: KnobProps['variant']; +}; + +export const useStyles = (props: UseStylesProps) => { + const {isPressed, isDisabled, isLoading, size, variant} = props; + const colors = useColors({variant}); + + const animation = useAnimation({ + type: 'timing', + initialValue: 0, + toValue: isPressed ? 1 : 0, + duration: 300, + useNativeDriver: false, + }); + + const loaderVisibilityAnimation = useAnimation({ + type: 'timing', + initialValue: 0, + toValue: isLoading ? 1 : 0, + duration: 150, + useNativeDriver: false, + }); + const spinningAnimation = useRef(new Animated.Value(0)); + + useEffect(() => { + Animated.loop( + Animated.timing(spinningAnimation.current, { + toValue: 1, + duration: 1000, + easing: Easing.linear, + useNativeDriver: true, + }), + ).start(); + }, []); + + return { + container: { + width: size === 'large' ? 48 : size === 'medium' ? 40 : 32, + height: size === 'large' ? 48 : size === 'medium' ? 40 : 32, + borderRadius: size === 'large' ? 48 : size === 'medium' ? 40 : 32, + backgroundColor: animation.interpolate({ + inputRange: [0, 1], + outputRange: [ + isDisabled + ? colors.containerBackground.disabled + : isLoading + ? colors.containerBackground.loading + : colors.containerBackground.idle, + colors.containerBackground.pressed, + ], + }), + }, + content: { + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + width: '100%', + height: '100%', + opacity: loaderVisibilityAnimation.interpolate({ + inputRange: [0, 1], + outputRange: [1, 0], + }), + } as Animated.WithAnimatedObject, + icon: { + color: isDisabled + ? colors.icon.disabled + : isLoading + ? colors.icon.loading + : isPressed + ? colors.icon.pressed + : colors.icon.idle, + }, + loaderWrapper: { + width: '100%', + height: '100%', + alignItems: 'center', + justifyContent: 'center', + opacity: loaderVisibilityAnimation, + position: 'absolute', + top: 0, + left: 0, + flexDirection: 'row', + } as Animated.WithAnimatedObject, + loaderContainer: { + alignItems: 'center', + justifyContent: 'center', + transform: [ + { + rotate: spinningAnimation.current.interpolate({ + inputRange: [0, 1], + outputRange: ['0deg', '360deg'], + }), + }, + ], + } as Animated.WithAnimatedObject, + loader: { + color: isDisabled ? colors.loader.disabled : colors.loader.idle, + trackColor: isDisabled + ? colors.loaderTrack.disabled + : colors.loaderTrack.idle, + }, + }; +}; diff --git a/src/ui/components/Knob/Knob.tsx b/src/ui/components/Knob/Knob.tsx new file mode 100644 index 0000000..889cce3 --- /dev/null +++ b/src/ui/components/Knob/Knob.tsx @@ -0,0 +1,95 @@ +import React, {FC, useMemo, useState} from 'react'; +import {Animated, TouchableHighlight} from 'react-native'; +// eslint-disable-next-line import/no-extraneous-dependencies +import Svg, {Path} from 'react-native-svg'; +// import {IconWhatsApp2} from '@wahed-tech/icons'; + +import {useMargin, useTestID} from '../../hooks'; +import {useStyles} from './Knob.styles'; +import {KnobProps} from './props'; + +export const Knob: FC = ({ + onPress, + size, + isDisabled, + isLoading, + icon: Icon, + testID, + variant = 'primary', + ...rest +}) => { + const margin = useMargin(rest); + const [isPressed, setIsPressed] = useState(false); + const styles = useStyles({ + isPressed, + size, + isDisabled, + isLoading, + variant, + }); + + const {getTestID} = useTestID({testID}); + const icon = useMemo(() => { + if (variant === 'whatsApp') { + return ( + <> + // + ); + } + if (!Icon) { + return null; + } + + return ( + !!Icon && ( + + ) + ); + }, [Icon, styles.icon, size, variant]); + + const loaderSize = size === 'large' ? 24 : size === 'medium' ? 20 : 16; + + return ( + setIsPressed(false)} + onShowUnderlay={() => setIsPressed(true)}> + + {icon} + + + + + + + + + + + ); +}; diff --git a/src/ui/components/Knob/index.ts b/src/ui/components/Knob/index.ts new file mode 100644 index 0000000..a2c6b5a --- /dev/null +++ b/src/ui/components/Knob/index.ts @@ -0,0 +1,2 @@ +export * from './Knob'; +export * from './props'; diff --git a/src/ui/components/Knob/props.ts b/src/ui/components/Knob/props.ts new file mode 100644 index 0000000..ec860a6 --- /dev/null +++ b/src/ui/components/Knob/props.ts @@ -0,0 +1,51 @@ +import {FC} from 'react'; +// import {IconProps} from '@wahed-tech/icons'; + +import {UseMarginProps, UseTestIDProps} from '../../hooks'; + +export type KnobProps = { + /** + * Web only: if set, will render a tag with href instead of button + */ + href?: string; + /** + * Handles knob press + */ + onPress?: () => void; + + /** + * If set to true, will put the component in disabled state + */ + isDisabled?: boolean; + + /** + * If set to true, will disable knob interaction and show loader + */ + isLoading?: boolean; + + /** + * Icon + */ + icon?: FC; + + /** + * Button size + * + * @default 'large' + */ + size?: 'large' | 'medium' | 'small'; + + /** + * variant + * + * @default 'primary' + */ + variant?: + | 'primary' + | 'secondary' + | 'tertiary' + | 'quaternary' + | 'quinary' + | 'whatsApp'; +} & UseMarginProps & + UseTestIDProps; diff --git a/src/ui/components/Modal/Modal.styles.ts b/src/ui/components/Modal/Modal.styles.ts new file mode 100644 index 0000000..c48a7cb --- /dev/null +++ b/src/ui/components/Modal/Modal.styles.ts @@ -0,0 +1,65 @@ +import {StyleSheet} from 'react-native'; + +import {toModalStyles} from './Modal.utils'; +import {ModalProps} from './props'; +import {Theme} from '../ThemeProvider'; +type UseStylesProps = { + variant: ModalProps['variant']; + isFooterVisible: boolean; + isCloseButtonVisible: boolean; +}; + +export const useStyles = ({ + variant, + isFooterVisible, + isCloseButtonVisible, +}: UseStylesProps) => { + const theme = Theme; + const wrapperProps = toModalStyles(variant); + + return StyleSheet.create({ + modal: { + flex: 1, + justifyContent: 'flex-end', + position: 'relative', + }, + overlay: { + position: 'absolute', + left: 0, + top: 0, + width: '100%', + height: '100%', + backgroundColor: theme.colors.overlay, + }, + wrapper: { + position: 'absolute', + width: '100%', + bottom: '-100%', + left: 0, + flexDirection: 'column', + ...wrapperProps, + }, + body: { + paddingTop: isCloseButtonVisible ? 0 : theme.spacing.xxl, + paddingBottom: isFooterVisible ? 0 : theme.spacing.xxl, + }, + illustration: { + alignSelf: 'center', + marginTop: theme.spacing.m, + color: theme.colors.darkGray, + }, + footer: { + marginTop: theme.spacing.xxl, + }, + closeButton: + variant === 'overlay' + ? { + alignSelf: 'flex-start', + paddingTop: 59, + paddingStart: 21, + } + : { + alignSelf: 'flex-end', + }, + }); +}; diff --git a/src/ui/components/Modal/Modal.tsx b/src/ui/components/Modal/Modal.tsx new file mode 100644 index 0000000..66d61c3 --- /dev/null +++ b/src/ui/components/Modal/Modal.tsx @@ -0,0 +1,54 @@ +import React, {FC, PropsWithChildren} from 'react'; + +import { + FooterButtonDouble, + FooterButtonDoubleProps, + FooterButtonSingle, + FooterButtonSingleProps, + ModalBody, + ModalBodyProps, + ModalDefault, + ModalOverlay, +} from './components'; +import {ModalProps} from './props'; + +export const Modal: FC> & { + FooterButtonDouble: FC; + FooterButtonSingle: FC; + Body: FC>; +} = ({ + testID, + variant = 'default', + isAnimated = true, + isVisible = false, + backgroundColor = 'white', + ...rest +}) => { + return variant === 'overlay' ? ( + + ) : ( + + ); +}; + +Modal.FooterButtonDouble = FooterButtonDouble; +Modal.FooterButtonSingle = FooterButtonSingle; +Modal.Body = ModalBody; diff --git a/src/ui/components/Modal/Modal.utils.ts b/src/ui/components/Modal/Modal.utils.ts new file mode 100644 index 0000000..a23b860 --- /dev/null +++ b/src/ui/components/Modal/Modal.utils.ts @@ -0,0 +1,25 @@ +import {ViewStyle} from 'react-native'; + +import {ModalProps} from './props'; + +/** + * Transforms modal variant to modal container styles + **/ +export const toModalStyles = (variant: ModalProps['variant']): ViewStyle => { + if (variant === 'full-height') { + return { + height: '100%', + paddingTop: 44, + overflow: 'hidden', + }; + } + if (variant === 'overlay') { + return { + height: '100%', + paddingTop: 0, + overflow: 'hidden', + }; + } + + return {}; +}; diff --git a/src/ui/components/Modal/components/FooterButtonDouble.tsx b/src/ui/components/Modal/components/FooterButtonDouble.tsx new file mode 100644 index 0000000..28f10e0 --- /dev/null +++ b/src/ui/components/Modal/components/FooterButtonDouble.tsx @@ -0,0 +1,50 @@ +import React, {FC} from 'react'; +import {View} from 'react-native'; + +import {Button} from '../../Button'; +import {Theme} from '../../ThemeProvider'; + +export type FooterButtonDoubleProps = { + /** + * Handles first button press + */ + onPressFirstButton: () => void; + /** + * label of first button + */ + firstButtonLabel: string; + /** + * Handles second button press + */ + onPressSecondButton: () => void; + /** + * label of second button + */ + secondButtonLabel: string; +}; + +export const FooterButtonDouble: FC = ({ + onPressFirstButton, + onPressSecondButton, + secondButtonLabel, + firstButtonLabel, +}) => { + const theme = Theme; + + return ( + + + + + ); +}; diff --git a/src/ui/components/Modal/components/FooterButtonSingle.tsx b/src/ui/components/Modal/components/FooterButtonSingle.tsx new file mode 100644 index 0000000..6b53141 --- /dev/null +++ b/src/ui/components/Modal/components/FooterButtonSingle.tsx @@ -0,0 +1,28 @@ +import React, {FC} from 'react'; +import {View} from 'react-native'; + +import {Button} from '../../Button'; + +export type FooterButtonSingleProps = { + /** + * Handles button press + */ + onPress: () => void; + /** + * label of button + */ + label: string; +}; + +export const FooterButtonSingle: FC = ({ + onPress, + label, +}) => { + return ( + + + + ); +}; diff --git a/src/ui/components/Modal/components/ModalBody.tsx b/src/ui/components/Modal/components/ModalBody.tsx new file mode 100644 index 0000000..f50a2b5 --- /dev/null +++ b/src/ui/components/Modal/components/ModalBody.tsx @@ -0,0 +1,67 @@ +import React, {FC, PropsWithChildren} from 'react'; +import {View} from 'react-native'; + +import {IconClose} from '../../Icons'; +import {UseTestIDProps} from '../../../hooks'; +import {Knob} from '../../Knob'; +import {Theme} from '../../ThemeProvider'; + +export type ModalBodyProps = { + /** + * Body variant + * 'default' -> there is margin between body and parent container borders + * 'full-width' -> no bottom and horizontal margin of the body + * 'full-height' -> body almost fully covers the screen + * + * @default 'default' + */ + variant?: 'default' | 'full-width' | 'full-height'; + + /** + * Body color + * + * @default "white" + */ + backgroundColor?: keyof (typeof Theme)['colors']; + + /** + * Handles close button press + **/ + onClose?: () => void; +} & UseTestIDProps; + +export const ModalBody: FC> = ({ + variant = 'default', + backgroundColor = 'white', + onClose, + children, +}) => { + const theme = Theme; + + return ( + + {!!onClose && ( + + + + )} + {children} + + ); +}; diff --git a/src/ui/components/Modal/components/ModalDefault.tsx b/src/ui/components/Modal/components/ModalDefault.tsx new file mode 100644 index 0000000..e865237 --- /dev/null +++ b/src/ui/components/Modal/components/ModalDefault.tsx @@ -0,0 +1,161 @@ +import React, {FC, PropsWithChildren, useEffect, useRef, useState} from 'react'; +import {Animated, Easing, Modal, Pressable, View} from 'react-native'; + +import {useTestID} from '../../../hooks'; +import {Text} from '../../Text'; +import {useStyles} from '../Modal.styles'; +import {ModalProps} from '../props'; +import {ModalBody} from './ModalBody'; + +const ModalDefaultAnimated: FC< + PropsWithChildren< + Omit & { + variant: 'default' | 'full-width' | 'full-height'; + onChangeVisibility(isVisible: boolean): void; + } + > +> = ({ + illustration, + isVisible, + isAnimated, + onClose, + variant, + backgroundColor, + title, + description, + children, + onChangeVisibility, + footer, +}) => { + const styles = useStyles({ + variant: variant, + isFooterVisible: !!footer, + isCloseButtonVisible: !!onClose, + }); + const fadeAnim = useRef(new Animated.Value(0)).current; + const topAnim = useRef(new Animated.Value(0)).current; + + useEffect(() => { + let visibilityTimeout: null | ReturnType = null; + if (isAnimated) { + Animated.timing(fadeAnim, { + toValue: isVisible ? 1 : 0, + duration: 300, + useNativeDriver: false, + }).start(); + Animated.timing(topAnim, { + toValue: isVisible ? 1 : 0, + duration: 300, + useNativeDriver: false, + easing: Easing.ease, + }).start(); + visibilityTimeout = setTimeout(() => { + onChangeVisibility(!!isVisible); + }, 300); + } else { + onChangeVisibility(!!isVisible); + } + + return () => { + if (visibilityTimeout === null) { + return; + } + + clearTimeout(visibilityTimeout); + }; + }, [isVisible, onChangeVisibility, isAnimated, fadeAnim, topAnim]); + + return ( + + + + + + + + {!!illustration && ( + {illustration} + )} + {!!title && ( + + {title} + + )} + {!!description && ( + + {description} + + )} + {children} + + {!!footer && {footer}} + + + + ); +}; + +export const ModalDefault: FC< + PropsWithChildren< + Omit & { + variant: 'default' | 'full-width' | 'full-height'; + } + > +> = props => { + const {getTestID} = useTestID({testID: props.testID}); + const [isVisible, setIsVisible] = useState(props.isVisible); + + useEffect(() => { + if (props.isVisible) { + setIsVisible(true); + } + }, [props.isVisible]); + + return ( + + + + ); +}; diff --git a/src/ui/components/Modal/components/ModalOverlay.tsx b/src/ui/components/Modal/components/ModalOverlay.tsx new file mode 100644 index 0000000..578b22e --- /dev/null +++ b/src/ui/components/Modal/components/ModalOverlay.tsx @@ -0,0 +1,85 @@ +import React, {FC, PropsWithChildren, useEffect, useRef, useState} from 'react'; +import {Animated, Modal, Pressable, View} from 'react-native'; + +import {IconClose} from '../../Icons'; +import {useTestID} from '../../../hooks'; +import {useStyles} from '../Modal.styles'; +import {ModalProps} from '../props'; +import {Theme} from '../../ThemeProvider'; + +export const ModalOverlay: FC> = ({ + backgroundColor = 'primary', + variant, + footer, + testID, + isVisible, + isAnimated, + onClose, + children, +}) => { + const [isShown, setIsShown] = useState(isVisible); + const styles = useStyles({ + variant: variant, + isFooterVisible: !!footer, + isCloseButtonVisible: !!onClose, + }); + const fadeAnim = useRef(new Animated.Value(1)).current; + const theme = Theme; + const {getTestID} = useTestID({testID: testID}); + + useEffect(() => { + let visibilityTimeout: null | ReturnType = null; + if (isAnimated) { + Animated.timing(fadeAnim, { + toValue: isShown ? 1 : 0, + duration: 600, + useNativeDriver: false, + }).start(); + visibilityTimeout = setTimeout(() => { + setIsShown(!!isShown); + }, 600); + } else { + setIsShown(!!isShown); + } + + return () => { + if (visibilityTimeout === null) { + return; + } + clearTimeout(visibilityTimeout); + }; + }, [isVisible, isShown, setIsShown, isAnimated, fadeAnim]); + + return ( + + + + + {!!onClose && ( + + + + )} + {children} + + + + + ); +}; diff --git a/src/ui/components/Modal/components/index.ts b/src/ui/components/Modal/components/index.ts new file mode 100644 index 0000000..93adc2c --- /dev/null +++ b/src/ui/components/Modal/components/index.ts @@ -0,0 +1,5 @@ +export * from './FooterButtonDouble'; +export * from './FooterButtonSingle'; +export * from './ModalDefault'; +export * from './ModalOverlay'; +export * from './ModalBody'; diff --git a/src/ui/components/Modal/index.ts b/src/ui/components/Modal/index.ts new file mode 100644 index 0000000..74701c9 --- /dev/null +++ b/src/ui/components/Modal/index.ts @@ -0,0 +1,2 @@ +export * from './Modal'; +export * from './props'; diff --git a/src/ui/components/Modal/props.ts b/src/ui/components/Modal/props.ts new file mode 100644 index 0000000..ee7a4b9 --- /dev/null +++ b/src/ui/components/Modal/props.ts @@ -0,0 +1,59 @@ +import {UseTestIDProps} from '../../hooks/useTestID/useTestID'; +import {Theme} from '../ThemeProvider'; + +export type ModalProps = { + /** + * title of modal + **/ + title?: string; + + /** + * description of modal + **/ + description?: string; + + /** + * JSX element icons etc + **/ + illustration?: JSX.Element; + + /** + * JSX element for footer single button or double button + **/ + footer?: JSX.Element; + + /** + * boolean flag to show and hide modal + **/ + isVisible?: boolean; + + /** + * Handles close button press + **/ + onClose?: () => void; + + /** + * Modal variant + * 'default' -> there is margin between modal container and screen borders + * 'full-width' -> no bottom and horizontal margin of the modal container + * 'full-height' -> modal container almost fully covers the screen + * 'overlay' -> modal container that covers the whole screen + * + * @default 'default' + */ + variant?: 'default' | 'full-width' | 'full-height' | 'overlay'; + + /** + * If set to true, the modal will have showing/closing animation + * + * @default true + */ + isAnimated?: boolean; + + /** + * Body background color + * + * @default "white" + */ + backgroundColor?: keyof (typeof Theme)['colors']; +} & UseTestIDProps; diff --git a/src/ui/components/Text/Text.styles.ts b/src/ui/components/Text/Text.styles.ts new file mode 100644 index 0000000..b3b2e5f --- /dev/null +++ b/src/ui/components/Text/Text.styles.ts @@ -0,0 +1,231 @@ +import {Platform} from 'react-native'; + +import {getFont, getWithDimension} from '../../utils'; +import {Theme} from '../ThemeProvider'; + +export const useStyles = () => { + const isMobile = Platform.OS !== 'web'; + const theme = Theme; + const font = getFont({ + languageCode: theme.languageCode, + isRtl: theme.direction === 'rtl', + }); + + return { + base: { + margin: 0, + padding: 0, + fontWeight: isMobile ? undefined : 'normal', + fontSize: 16, + lineHeight: getWithDimension(20), + color: theme.colors.darkGray, + fontFamily: font.regular, + textAlignVertical: 'center', + }, + ellipsizeTail: { + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + }, + textAlign: { + center: { + textAlign: 'center', + }, + end: { + textAlign: isMobile ? 'right' : 'end', + }, + start: { + textAlign: isMobile ? 'left' : 'start', + }, + }, + variants: { + mainHeading1: { + fontWeight: isMobile ? undefined : '600', + fontSize: 64, + lineHeight: getWithDimension(84), + fontFamily: font.semiBold, + }, + mainHeading2: { + fontWeight: isMobile ? undefined : '600', + fontSize: 48, + lineHeight: getWithDimension(64), + fontFamily: font.semiBold, + }, + headline1: { + fontWeight: isMobile ? undefined : 'bold', + fontSize: 34, + lineHeight: getWithDimension(46), + fontFamily: font.bold, + }, + headline2: { + fontSize: 30, + fontWeight: isMobile ? undefined : 'bold', + lineHeight: getWithDimension(40), + fontFamily: font.bold, + }, + headline3: { + fontSize: 24, + fontWeight: isMobile ? undefined : 'bold', + lineHeight: getWithDimension(32), + fontFamily: font.bold, + }, + headline4: { + fontSize: 20, + fontWeight: isMobile ? undefined : 'bold', + lineHeight: getWithDimension(28), + fontFamily: font.bold, + }, + headline5: { + fontSize: 18, + fontWeight: isMobile ? undefined : 'bold', + lineHeight: getWithDimension(24), + fontFamily: font.bold, + }, + + label1: { + fontSize: 18, + fontWeight: isMobile ? undefined : '500', + lineHeight: getWithDimension(28), + fontFamily: font.interMedium, + }, + label2: { + fontSize: 16, + fontWeight: isMobile ? undefined : '500', + lineHeight: getWithDimension(24), + fontFamily: font.interMedium, + }, + label3: { + fontSize: 14, + fontWeight: isMobile ? undefined : '500', + lineHeight: getWithDimension(24), + fontFamily: font.interMedium, + }, + label4: { + fontSize: 12, + fontWeight: isMobile ? undefined : '500', + lineHeight: getWithDimension(24), + fontFamily: font.interMedium, + }, + body1: { + fontSize: 20, + lineHeight: getWithDimension(28), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.regular1, + }, + body1underlined: { + fontSize: 20, + lineHeight: getWithDimension(28), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.regular1, + textDecorationLine: 'underline', + }, + body1italics: { + fontSize: 20, + lineHeight: getWithDimension(28), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.italic, + fontStyle: 'italic', + }, + body2: { + fontSize: 18, + lineHeight: getWithDimension(24), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.regular1, + }, + body2underlined: { + fontSize: 18, + lineHeight: getWithDimension(24), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.regular1, + textDecorationLine: 'underline', + }, + body2italics: { + fontSize: 18, + lineHeight: getWithDimension(24), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.italic, + fontStyle: 'italic', + }, + body3: { + fontSize: 16, + lineHeight: getWithDimension(20), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.regular1, + }, + body3underlined: { + fontSize: 16, + lineHeight: getWithDimension(20), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.regular1, + textDecorationLine: 'underline', + }, + body3italics: { + fontSize: 16, + lineHeight: getWithDimension(20), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.italic, + fontStyle: 'italic', + }, + body4: { + fontSize: 14, + lineHeight: getWithDimension(20), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.regular1, + }, + body4underlined: { + fontSize: 14, + lineHeight: getWithDimension(20), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.regular1, + textDecorationLine: 'underline', + }, + body4italics: { + fontSize: 14, + lineHeight: getWithDimension(20), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.italic, + fontStyle: 'italic', + }, + body5: { + fontSize: 12, + lineHeight: getWithDimension(18), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.regular1, + }, + body5underlined: { + fontSize: 12, + lineHeight: getWithDimension(18), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.regular1, + textDecorationLine: 'underline', + }, + body5italics: { + fontSize: 12, + lineHeight: getWithDimension(18), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.italic, + fontStyle: 'italic', + }, + body6: { + fontSize: 10, + lineHeight: getWithDimension(14), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.regular1, + }, + body6underlined: { + fontSize: 10, + lineHeight: getWithDimension(14), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.regular1, + textDecorationLine: 'underline', + }, + body6italics: { + fontSize: 10, + lineHeight: getWithDimension(14), + fontWeight: isMobile ? undefined : '400', + fontFamily: font.italic, + fontStyle: 'italic', + }, + }, + }; +}; diff --git a/src/ui/components/Text/Text.tsx b/src/ui/components/Text/Text.tsx new file mode 100644 index 0000000..75d2b10 --- /dev/null +++ b/src/ui/components/Text/Text.tsx @@ -0,0 +1,47 @@ +import React, {FC, PropsWithChildren} from 'react'; +import {Text as BaseText, StyleProp, TextStyle} from 'react-native'; + +import {useMargin, useTestID} from '../../hooks'; +import {Theme} from '../ThemeProvider'; +import {TextProps} from './props'; +import {useStyles} from './Text.styles'; + +export const Text: FC> = ({ + children, + color, + variant = 'body2', + textAlign, + ellipsizeMode, + numberOfLines = 1, + onPress, + testID, + ...restProps +}) => { + const {getTestID} = useTestID({testID}); + const theme = Theme; + const margin = useMargin(restProps); + + const styles = useStyles(); + const componentStyles = [ + styles.base, + styles.variants[variant], + !!textAlign && styles.textAlign[textAlign], + !!color && { + color: theme.colors[color], + }, + ...margin, + ] as StyleProp; + + return ( + + {children} + + ); +}; diff --git a/src/ui/components/Text/index.ts b/src/ui/components/Text/index.ts new file mode 100644 index 0000000..6a6ca5c --- /dev/null +++ b/src/ui/components/Text/index.ts @@ -0,0 +1,2 @@ +export * from './Text'; +export * from './props'; diff --git a/src/ui/components/Text/props.ts b/src/ui/components/Text/props.ts new file mode 100644 index 0000000..9cb983d --- /dev/null +++ b/src/ui/components/Text/props.ts @@ -0,0 +1,89 @@ +import {Theme} from '../../components/ThemeProvider'; +import {UseMarginProps, UseTestIDProps} from '../../hooks'; +export type TextProps = { + /** + * Web only: if set, will change html tag of the component + */ + as?: 'h1' | 'h2' | 'h3' | 'p' | 'span' | 'strong'; + /** + * Variant of the text from design system + */ + variant?: UseStyles['variants']; + /** + * Color of the text from design system + */ + color?: keyof (typeof Theme)['colors']; + + /** + * Text alignment + * + * `start` - text will be aligned to the left if ltr or right if rtl + * `end` - text will be aligned to the right if ltr or left if rtl + * `center` - text will be centered + */ + textAlign?: UseStyles['textAlign']; + + /** + * Where to show ellipsis + * + * `tail` - abcd... + */ + ellipsizeMode?: 'tail'; + + /** + * If set, will truncate the text to `numberOfLines` lines. + * Will only work if `ellipsizeMode` is set + */ + numberOfLines?: number; + + /** + * Handles press events. + */ + onPress?: () => void; +} & UseMarginProps & + UseTestIDProps; + +type UseStyles = { + /** + * Variant of the text from design system + */ + variants: + | 'mainHeading1' + | 'mainHeading2' + | 'headline1' + | 'headline2' + | 'headline3' + | 'headline4' + | 'headline5' + | 'label1' + | 'label2' + | 'label3' + | 'label4' + | 'body1' + | 'body1underlined' + | 'body1italics' + | 'body2' + | 'body2underlined' + | 'body2italics' + | 'body3' + | 'body3underlined' + | 'body3italics' + | 'body4' + | 'body4underlined' + | 'body4italics' + | 'body5' + | 'body5underlined' + | 'body5italics' + | 'body6' + | 'body6underlined' + | 'body6italics'; + + /** + * Text alignment + * + * `start` - text will be aligned to the left if ltr or right if rtl + * `end` - text will be aligned to the right if ltr or left if rtl + * `center` - text will be centered + */ + textAlign: 'start' | 'end' | 'center'; +}; diff --git a/src/ui/components/ThemeProvider/ThemeProvider.tsx b/src/ui/components/ThemeProvider/ThemeProvider.tsx new file mode 100644 index 0000000..6d8480b --- /dev/null +++ b/src/ui/components/ThemeProvider/ThemeProvider.tsx @@ -0,0 +1,10 @@ +import React, {FC, PropsWithChildren} from 'react'; +import {ThemeProvider as BaseThemeProvider, Theme} from '@emotion/react'; + +export const ThemeProvider: FC< + PropsWithChildren<{ + theme: Theme; + }> +> = ({theme, children}) => { + return {children}; +}; diff --git a/src/ui/components/ThemeProvider/index.ts b/src/ui/components/ThemeProvider/index.ts new file mode 100644 index 0000000..81fd4ba --- /dev/null +++ b/src/ui/components/ThemeProvider/index.ts @@ -0,0 +1,2 @@ +export * from './ThemeProvider'; +export * from './theme'; diff --git a/src/ui/components/ThemeProvider/theme.ts b/src/ui/components/ThemeProvider/theme.ts new file mode 100644 index 0000000..12be3fc --- /dev/null +++ b/src/ui/components/ThemeProvider/theme.ts @@ -0,0 +1,123 @@ +export const Theme = { + colors: { + primary: '#F8D234', + secondary: '#BDC1C2', + primaryDark1: '#FFE987', + primaryDark2: '#FFDF54', + primary3: '#E6C94C', + primary4: '#CCB243', + primary5: '#B39C3B', + primary6: '#FFF2BB', + primary7: '#FFF9DD', + darkGray: '#303030', + lightGray2: '#585858', + lightGray3: '#868686', + lightGray4: '#B1B1B1', + lightGray5: '#D9D9D9', + lightGray6: '#F6F6F6', + lightGray7: '#FBFBFB', + lightGray8: '#ECECEC', + white: '#ffffff', + error: '#E45F60', + errorLight: '#F4BFBF', + errorDark: '#CD5656', + errorExtraDark: '#B64C4D', + error1: '#A04343', + error4: '#EC8F90', + error6: '#FADFDF', + approved: '#2ECC71', + approvedLight: '#D5F5E3', + approved1: '#208F4F', + approved2: '#25A35A', + approved3: '#29B866', + approved5: '#6DDB9C', + approved6: '#97E6B8', + link: '#87C0C1', + linkLight: '#D8EDEE', + linkExtraLight: '#F9FFFF', + linkDark: '#689E9F', + linkExtraDark: '#4D7C7D', + link1: '#345A5B', + link5: '#A8E1E3', + info: '#F67B46', + info1: '#AC5631', + info2: '#C56238', + info3: '#DD6F3F', + info4: '#F9A37E', + info5: '#FBCAB5', + info6: '#FDE5DA', + confetti1: '#3FE0C1', + confetti2: '#FFAEC3', + confetti3: '#D18DF5', + confetti4: '#E565B2', + confetti5: '#A19CFF', + confetti6: '#5AE0FF', + accent: '#DD7ADF', + accent1: '#9B559C', + accent2: '#B162B2', + accent3: '#C76EC9', + accent4: '#E7A2E9', + accent5: '#F1CAF2', + accent6: '#F8E4F9', + sky1: '#313C50', + sky2: '#4F5F7D', + sky3: '#7284A8', + sky4: '#8A9EC5', + sky5: '#A4B9E2', + sky6: '#BFD5FF', + sky7: '#EBF2FF', + coral: '#E4BA9E', + coral1: '#A0826F', + coral2: '#B6957E', + coral3: '#CDA78E', + coral4: '#EACCB8', + coral5: '#F1E1DB', + coral6: '#F6EDEA', + allocation1: '#F8D234', + allocation2: '#908CFF', + allocation3: '#82B1FF', + allocation4: '#6DDB9C', + allocation5: '#F48785', + allocation6: '#4DB6AC', + overlay: 'rgba(48, 48, 48, 0.3)', + overlay1: 'rgba(36, 56, 54, 0.95)', + overlay2: 'rgba(0, 0, 0, 0.9)', + purpleTawhid: '#C4C1DE', + hikmaGreen: '#A1C9CB', + content1: '#F3E772', + content2: '#C4C1DE', + content3: '#A1C9CB', + content4: '#B1C8DF', + content5: '#DEB1DF', + content6: '#DFB1B4', + content7: '#C2DEC1', + content8: '#DECFC1', + blue: '#F1F5FD', + blue1: '#D3DFF8', + blue2: '#BED0F4', + blue3: '#A0BAF0', + blue4: '#8DADED', + blue5: '#7198E8', + blue6: '#678AD3', + blue7: '#506CA5', + blue8: '#3E5480', + blue9: '#2F4061', + overlayGreen: ' rgba(120, 156, 142, 0.2)', + overlaylightBrown: 'rgba(135, 94, 100, 0.2)', + }, + spacing: { + xxxs: 4, + xxs: 8, + xs: 12, + s: 16, + m: 24, + l: 32, + xl: 40, + xxl: 48, + xxxl: 64, + }, + // Language code + languageCode: 'en', + // Layout direction. To be rewritten in ThemeProvider + direction: 'ltr', +}; diff --git a/src/ui/components/index.ts b/src/ui/components/index.ts new file mode 100644 index 0000000..d1ad1c5 --- /dev/null +++ b/src/ui/components/index.ts @@ -0,0 +1,3 @@ +export * from './Text'; +export * from './InputText'; +export * from './Button'; diff --git a/src/ui/fonts/Inter-Bold.ttf b/src/ui/fonts/Inter-Bold.ttf new file mode 100644 index 0000000..f13d511 Binary files /dev/null and b/src/ui/fonts/Inter-Bold.ttf differ diff --git a/src/ui/fonts/Inter-Medium.ttf b/src/ui/fonts/Inter-Medium.ttf new file mode 100644 index 0000000..9a3396f Binary files /dev/null and b/src/ui/fonts/Inter-Medium.ttf differ diff --git a/src/ui/fonts/Inter-Regular.ttf b/src/ui/fonts/Inter-Regular.ttf new file mode 100644 index 0000000..2c164bb Binary files /dev/null and b/src/ui/fonts/Inter-Regular.ttf differ diff --git a/src/ui/fonts/Inter-SemiBold.ttf b/src/ui/fonts/Inter-SemiBold.ttf new file mode 100644 index 0000000..b974371 Binary files /dev/null and b/src/ui/fonts/Inter-SemiBold.ttf differ diff --git a/src/ui/fonts/Inter-italic.ttf b/src/ui/fonts/Inter-italic.ttf new file mode 100644 index 0000000..5b4ac27 Binary files /dev/null and b/src/ui/fonts/Inter-italic.ttf differ diff --git a/src/ui/fonts/URWGeometricArabic-Bold.ttf b/src/ui/fonts/URWGeometricArabic-Bold.ttf new file mode 100644 index 0000000..74a2a70 Binary files /dev/null and b/src/ui/fonts/URWGeometricArabic-Bold.ttf differ diff --git a/src/ui/fonts/URWGeometricArabic-Medium.ttf b/src/ui/fonts/URWGeometricArabic-Medium.ttf new file mode 100644 index 0000000..99d7ea7 Binary files /dev/null and b/src/ui/fonts/URWGeometricArabic-Medium.ttf differ diff --git a/src/ui/fonts/URWGeometricArabic-Regular.ttf b/src/ui/fonts/URWGeometricArabic-Regular.ttf new file mode 100644 index 0000000..0498542 Binary files /dev/null and b/src/ui/fonts/URWGeometricArabic-Regular.ttf differ diff --git a/src/ui/fonts/URWGeometricArabic-SemiBold.ttf b/src/ui/fonts/URWGeometricArabic-SemiBold.ttf new file mode 100644 index 0000000..62ddbf7 Binary files /dev/null and b/src/ui/fonts/URWGeometricArabic-SemiBold.ttf differ diff --git a/src/ui/hocs/withAnimation.tsx b/src/ui/hocs/withAnimation.tsx new file mode 100644 index 0000000..0cc2ded --- /dev/null +++ b/src/ui/hocs/withAnimation.tsx @@ -0,0 +1,26 @@ +import React from 'react'; +import {Animated} from 'react-native'; + +/** + * Currently Animated.createAnimatedComponent only supports class components. + * Therefore in order to animate custom component, it should be wrapped within + * class component + * @param Component - Component to wrap within class component + * @returns Class component which acts as a wrapper for provided Component + */ +const wrapWithinClassComponent =

( + component: React.ComponentType

, +) => + class ComponentWithAnimation extends React.Component

{ + render(): React.ReactNode { + const Component = component; + + return ; + } + }; + +export const withAnimation =

( + component: React.ComponentType

, +) => { + return Animated.createAnimatedComponent(wrapWithinClassComponent(component)); +}; diff --git a/src/ui/hooks/index.ts b/src/ui/hooks/index.ts new file mode 100644 index 0000000..d2a5317 --- /dev/null +++ b/src/ui/hooks/index.ts @@ -0,0 +1,4 @@ +export * from './useTheme'; +export * from './useMargin'; +export * from './usePadding'; +export * from './useTestID'; diff --git a/src/ui/hooks/useMargin/index.ts b/src/ui/hooks/useMargin/index.ts new file mode 100644 index 0000000..cbdc81a --- /dev/null +++ b/src/ui/hooks/useMargin/index.ts @@ -0,0 +1,2 @@ +export * from './useMargin'; +export * from './types'; diff --git a/src/ui/hooks/useMargin/types.ts b/src/ui/hooks/useMargin/types.ts new file mode 100644 index 0000000..953f6c7 --- /dev/null +++ b/src/ui/hooks/useMargin/types.ts @@ -0,0 +1,36 @@ +import {Theme} from '../../components/ThemeProvider'; + +export type Spacing = keyof (typeof Theme)['spacing']; + +export type SpacingWithNegative = `-${Spacing}` | Spacing; + +export type UseMarginProps = { + /** + * Margin around the component + */ + margin?: `-${Spacing}` | Spacing; + /** + * Margin on the left side of the component + */ + marginStart?: `-${Spacing}` | Spacing; + /** + * Margin on the right side of the component + */ + marginEnd?: `-${Spacing}` | Spacing; + /** + * Margin on the top side of the component + */ + marginTop?: `-${Spacing}` | Spacing; + /** + * Margin on the bottom side of the component + */ + marginBottom?: `-${Spacing}` | Spacing; + /** + * Margin on the top and bottom side of the component + */ + marginVertical?: `-${Spacing}` | Spacing; + /** + * Margin on the left and right side of the component + */ + marginHorizontal?: `-${Spacing}` | Spacing; +}; diff --git a/src/ui/hooks/useMargin/useMargin.ts b/src/ui/hooks/useMargin/useMargin.ts new file mode 100644 index 0000000..991fbc9 --- /dev/null +++ b/src/ui/hooks/useMargin/useMargin.ts @@ -0,0 +1,74 @@ +import {Platform} from 'react-native'; + +import {SpacingWithNegative, UseMarginProps} from './types'; +import {Theme} from '../../components/ThemeProvider'; + +/** + * This hook returns the margin styles for the component. Those styles include: + * - `margin` - Margin around the component. + * - `marginStart` - Margin on the left side of the component. + * - `marginEnd` - Margin on the right side of the component. + * - `marginTop` - Margin on the top of the component. + * - `marginBottom` - Margin on the bottom of the component. + * - `marginVertical` - Margin on the top and bottom of the component. + * - `marginHorizontal` - Margin on the left and right of the component. + */ +export const useMargin = ({ + margin, + marginBottom, + marginStart, + marginEnd, + marginTop, + marginHorizontal, + marginVertical, +}: UseMarginProps) => { + const theme = Theme; + + const isWeb = Platform.OS === 'web'; + + const toSpacing = (spacing: SpacingWithNegative) => { + if (spacing.includes('-')) { + return -theme.spacing[ + spacing.slice(1, spacing.length) as keyof typeof theme.spacing + ]; + } + + return theme.spacing[spacing as keyof typeof theme.spacing]; + }; + + return [ + !!margin && { + margin: toSpacing(margin), + }, + !!marginVertical && { + marginTop: toSpacing(marginVertical), + marginBottom: toSpacing(marginVertical), + }, + !!marginHorizontal && { + marginLeft: toSpacing(marginHorizontal), + marginRight: toSpacing(marginHorizontal), + }, + !!marginBottom && { + marginBottom: toSpacing(marginBottom), + }, + !!marginTop && { + marginTop: toSpacing(marginTop), + }, + !!marginStart && + (isWeb + ? { + marginInlineStart: toSpacing(marginStart), + } + : { + marginLeft: toSpacing(marginStart), + }), + !!marginEnd && + (isWeb + ? { + marginInlineEnd: toSpacing(marginEnd), + } + : { + marginRight: toSpacing(marginEnd), + }), + ]; +}; diff --git a/src/ui/hooks/usePadding/index.ts b/src/ui/hooks/usePadding/index.ts new file mode 100644 index 0000000..b4887df --- /dev/null +++ b/src/ui/hooks/usePadding/index.ts @@ -0,0 +1,2 @@ +export * from './usePadding'; +export * from './types'; diff --git a/src/ui/hooks/usePadding/types.ts b/src/ui/hooks/usePadding/types.ts new file mode 100644 index 0000000..b7c9142 --- /dev/null +++ b/src/ui/hooks/usePadding/types.ts @@ -0,0 +1,32 @@ +import {Theme} from '../../components/ThemeProvider'; + +export type UsePaddingProps = { + /** + * Padding around the component + */ + padding?: keyof (typeof Theme)['spacing']; + /** + * Padding on the left side of the component + */ + paddingStart?: keyof (typeof Theme)['spacing']; + /** + * Padding on the right side of the component + */ + paddingEnd?: keyof (typeof Theme)['spacing']; + /** + * Padding on the top side of the component + */ + paddingTop?: keyof (typeof Theme)['spacing']; + /** + * Padding on the bottom side of the component + */ + paddingBottom?: keyof (typeof Theme)['spacing']; + /** + * Padding on the top and bottom side of the component + */ + paddingVertical?: keyof (typeof Theme)['spacing']; + /** + * Padding on the left and right side of the component + */ + paddingHorizontal?: keyof (typeof Theme)['spacing']; +}; diff --git a/src/ui/hooks/usePadding/usePadding.ts b/src/ui/hooks/usePadding/usePadding.ts new file mode 100644 index 0000000..428d7bf --- /dev/null +++ b/src/ui/hooks/usePadding/usePadding.ts @@ -0,0 +1,66 @@ +import {Platform} from 'react-native'; + +import {useTheme} from '../useTheme'; +import {UsePaddingProps} from './types'; + +/** + * This hook returns the padding styles for the component. Those styles include: + * - `padding` - Padding around the component. + * - `paddingStart` - Padding on the left side of the component. + * - `paddingEnd` - Padding on the right side of the component. + * - `paddingTop` - Padding on the top of the component. + * - `paddingBottom` - Padding on the bottom of the component. + * - `paddingVertical` - Padding on the top and bottom of the component. + * - `paddingHorizontal` - Padding on the left and right of the component. + */ +export const usePadding = (props: UsePaddingProps) => { + const theme = useTheme(); + + const { + padding, + paddingBottom, + paddingStart, + paddingEnd, + paddingTop, + paddingHorizontal, + paddingVertical, + } = props; + + const isWeb = Platform.OS === 'web'; + + return [ + !!padding && { + padding: theme.spacing[padding], + }, + !!paddingVertical && { + paddingTop: theme.spacing[paddingVertical], + paddingBottom: theme.spacing[paddingVertical], + }, + !!paddingHorizontal && { + paddingLeft: theme.spacing[paddingHorizontal], + paddingRight: theme.spacing[paddingHorizontal], + }, + !!paddingBottom && { + paddingBottom: theme.spacing[paddingBottom], + }, + !!paddingTop && { + paddingTop: theme.spacing[paddingTop], + }, + !!paddingStart && + (isWeb + ? { + paddingInlineStart: theme.spacing[paddingStart], + } + : { + paddingLeft: theme.spacing[paddingStart], + }), + !!paddingEnd && + (isWeb + ? { + paddingInlineEnd: theme.spacing[paddingEnd], + } + : { + paddingRight: theme.spacing[paddingEnd], + }), + ]; +}; diff --git a/src/ui/hooks/useRipple/index.ts b/src/ui/hooks/useRipple/index.ts new file mode 100644 index 0000000..753c71b --- /dev/null +++ b/src/ui/hooks/useRipple/index.ts @@ -0,0 +1 @@ +export * from './useRipple'; diff --git a/src/ui/hooks/useRipple/types.d.ts b/src/ui/hooks/useRipple/types.d.ts new file mode 100644 index 0000000..9f33fc8 --- /dev/null +++ b/src/ui/hooks/useRipple/types.d.ts @@ -0,0 +1,45 @@ +export type UseRippleProps = { + /** + * Ripple width + * If not set then will be 2x of ripple container + */ + width?: number; + /** + * Ripple height + * If not set then will be 2x of ripple container + */ + height?: number; + + /** + * Ripple color + * Black by default + */ + color?: string; + + /** + * Ripple max opacity + * from 0 to 1. + * Default is 0.5 + */ + maxOpacity?: number; +}; + +export type UseRippleReturn = { + /** + * Ripple jsx. Please place it within relatively positioned container. + * It will use container's width and height. + */ + ripple: React.ReactElement | null; + + /** + * This callback controls the start of the ripple effect. + * Place this callback onto the pressable component pressIn prop + */ + onPressIn(): void; + + /** + * This callback controls the stop of the ripple effect. + * Place this callback onto the pressable component pressIn prop + */ + onPressOut(): void; +}; diff --git a/src/ui/hooks/useRipple/useRipple.tsx b/src/ui/hooks/useRipple/useRipple.tsx new file mode 100644 index 0000000..6a1ad84 --- /dev/null +++ b/src/ui/hooks/useRipple/useRipple.tsx @@ -0,0 +1,80 @@ +import React, {useMemo, useRef, useState} from 'react'; +import {Animated, Easing, LayoutChangeEvent, View} from 'react-native'; + +import {UseRippleProps, UseRippleReturn} from './types'; + +/** + * Creates nice ripple effect within the container + * @see https://medium.com/react-native-motion/ripple-effect-in-react-native-1cb0ad568e91 + */ +export const useRipple = (props?: UseRippleProps): UseRippleReturn => { + const {color = 'black', maxOpacity = 0.5, height, width} = props || {}; + const scale = useRef(new Animated.Value(0.01)); + const opacity = useRef(new Animated.Value(maxOpacity)); + const [containerDimensions, setContainerDimensions] = useState<{ + width: number; + height: number; + } | null>(null); + + const onPressIn = () => { + Animated.timing(scale.current, { + toValue: 1, + duration: 130, + easing: Easing.bezier(0.0, 0.0, 0.1, 1), + useNativeDriver: true, + }).start(); + }; + + const onPressOut = () => { + Animated.timing(opacity.current, { + toValue: 0, + duration: 200, + useNativeDriver: true, + }).start(() => { + scale.current.setValue(0.01); + opacity.current.setValue(maxOpacity); + }); + }; + + const onRippleContainerLayout = (event: LayoutChangeEvent) => { + setContainerDimensions({ + width: event.nativeEvent.layout.width, + height: event.nativeEvent.layout.width, + }); + }; + + const ripple = useMemo(() => { + return ( + + {!!containerDimensions && ( + + )} + + ); + }, [containerDimensions, width, height, color]); + + return { + onPressIn, + onPressOut, + ripple, + }; +}; diff --git a/src/ui/hooks/useTestID/index.ts b/src/ui/hooks/useTestID/index.ts new file mode 100644 index 0000000..4918671 --- /dev/null +++ b/src/ui/hooks/useTestID/index.ts @@ -0,0 +1,2 @@ +export * from './useTestId'; +export * from './props'; diff --git a/src/ui/hooks/useTestID/props.ts b/src/ui/hooks/useTestID/props.ts new file mode 100644 index 0000000..75c2ca9 --- /dev/null +++ b/src/ui/hooks/useTestID/props.ts @@ -0,0 +1,6 @@ +export type UseTestIDProps = { + /** + * Used to locate this view in end-to-end tests. + */ + testID?: string; +}; diff --git a/src/ui/hooks/useTestID/useTestId.ts b/src/ui/hooks/useTestID/useTestId.ts new file mode 100644 index 0000000..89e9197 --- /dev/null +++ b/src/ui/hooks/useTestID/useTestId.ts @@ -0,0 +1,9 @@ +import {UseTestIDProps} from './props'; + +export * from './props'; + +export const useTestID = ({testID = ''}: UseTestIDProps) => { + return { + getTestID: (prefix: string) => (testID ? `${prefix}-${testID}` : prefix), + }; +}; diff --git a/src/ui/hooks/useTheme.ts b/src/ui/hooks/useTheme.ts new file mode 100644 index 0000000..2a3cf92 --- /dev/null +++ b/src/ui/hooks/useTheme.ts @@ -0,0 +1,8 @@ +import {useTheme as baseUseTheme} from '@emotion/react'; + +import {Theme} from '../components/ThemeProvider'; + +/** + * This hook returns a theme object which contains all the theme variables + */ +export const useTheme: () => typeof Theme = baseUseTheme as () => typeof Theme; diff --git a/src/ui/index.tsx b/src/ui/index.tsx new file mode 100644 index 0000000..c55977d --- /dev/null +++ b/src/ui/index.tsx @@ -0,0 +1,3 @@ +export * from './components'; +export * from './hooks'; +export * from './utils'; diff --git a/src/ui/style.css b/src/ui/style.css new file mode 100644 index 0000000..58024fe --- /dev/null +++ b/src/ui/style.css @@ -0,0 +1,64 @@ +@font-face { + font-family: 'URWGeometricArabic-Regular'; + src: local('URWGeometricArabic-Regular'), + url(./assets/fonts/URWGeometricArabic-Regular.ttf) format('opentype'); +} + +@font-face { + font-family: 'URWGeometricArabic-Bold'; + font-weight: bold; + src: local('URWGeometricArabic-Bold'), + url(./assets/fonts/URWGeometricArabic-Bold.ttf) format('opentype'); +} + +@font-face { + font-family: 'URWGeometricArabic-Medium'; + font-weight: 500; + src: local('URWGeometricArabic-Medium'), + url(./assets/fonts/URWGeometricArabic-Medium.ttf) format('opentype'); +} + +@font-face { + font-family: 'URWGeometricArabic-SemiBold'; + font-weight: 600; + src: local('URWGeometricArabic-SemiBold'), + url(./assets/fonts/URWGeometricArabic-SemiBold.ttf) format('opentype'); +} +@font-face { + font-family: 'Inter-Regular'; + src: local('Inter-Regular'), + url(./assets/fonts/Inter-Regular.ttf) format('truetype'); +} + +@font-face { + font-family: 'Inter-Bold'; + font-weight: bold; + src: local('Inter-Bold'), + url(./assets/fonts/Inter-Bold.ttf) format('truetype'); +} + +@font-face { + font-family: 'Inter-Medium'; + font-weight: 500; + src: local('Inter-Medium'), + url(./assets/fonts/Inter-Medium.ttf) format('truetype'); +} + +@font-face { + font-family: 'Inter-Semibold'; + font-weight: 600; + src: local('Inter-Semibold'), + url(./assets/fonts/Inter-SemiBold.ttf) format('truetype'); +} + +@font-face { + font-family: 'Inter-Italic'; + font-style: 'italic'; + src: local('Inter-Italic'), + url(./assets/fonts/Inter-italic.ttf) format('truetype'); +} + +body { + font-family: 'URWGeometricArabic-Regular', -apple-system, Montserrat, + sans-serif; +} diff --git a/src/ui/types/AccessibilityProps.ts b/src/ui/types/AccessibilityProps.ts new file mode 100644 index 0000000..a43b43b --- /dev/null +++ b/src/ui/types/AccessibilityProps.ts @@ -0,0 +1,21 @@ +import {AccessibilityRole} from 'react-native'; + +export type AccessibilityProps = { + /** + * Communicates the purpose of component e.g. button, alert + * If set, will add role to element + */ + role?: AccessibilityRole; + + /** + * Tells user what element has been selected + * When set, VoiceOver reads string when user selects element + */ + label?: string; + + /** + * Helps users understand what will happen when they perform an action. + * Set when the result is not clear from the accessibility label. + */ + hint?: string; +}; diff --git a/src/ui/types/index.d.ts b/src/ui/types/index.d.ts new file mode 100644 index 0000000..16cfc6c --- /dev/null +++ b/src/ui/types/index.d.ts @@ -0,0 +1,19 @@ +// For React Native +declare module '*.svg' { + import React from 'react'; + import {SvgProps} from 'react-native-svg'; + const content: React.FC; + export default content; +} + +// For Web +declare module '*.svg' { + import * as React from 'react'; + + export const ReactComponent: React.FunctionComponent< + React.SVGProps & {title?: string} + >; + + const src: string; + export default src; +} diff --git a/src/ui/utils/getFont.ts b/src/ui/utils/getFont.ts new file mode 100644 index 0000000..49af737 --- /dev/null +++ b/src/ui/utils/getFont.ts @@ -0,0 +1,33 @@ +type GetFontFunction = (props: {languageCode: string; isRtl: boolean}) => { + regular: string; + bold: string; + medium: string; + semiBold: string; + regular1?: string; + italic?: string; + interMedium?: string; +}; + +export const getFont: GetFontFunction = props => { + const {languageCode} = props; + if (languageCode === 'ru') { + return { + regular: 'Inter-Regular', + bold: 'Inter-Bold', + medium: 'Inter-Medium', + semiBold: 'Inter-SemiBold', + regular1: 'Inter-Regular', + italic: 'Inter-Italic', + }; + } + + return { + regular: 'URWGeometricArabic-Regular', + bold: 'URWGeometricArabic-Bold', + medium: 'URWGeometricArabic-Medium', + semiBold: 'URWGeometricArabic-SemiBold', + interMedium: 'Inter-Medium', + regular1: 'Inter-Regular', + italic: 'Inter-Italic', + }; +}; diff --git a/src/ui/utils/getWithDimension.ts b/src/ui/utils/getWithDimension.ts new file mode 100644 index 0000000..817a2a2 --- /dev/null +++ b/src/ui/utils/getWithDimension.ts @@ -0,0 +1,11 @@ +import {Platform} from 'react-native'; + +export const getWithDimension = (value: number) => { + const isMobile = Platform.OS !== 'web'; + + if (isMobile) { + return value; + } + + return value + 'px'; +}; diff --git a/src/ui/utils/index.ts b/src/ui/utils/index.ts new file mode 100644 index 0000000..ef89324 --- /dev/null +++ b/src/ui/utils/index.ts @@ -0,0 +1,2 @@ +export * from './getFont'; +export * from './getWithDimension'; diff --git a/src/utils/constants.tsx b/src/utils/constants.tsx new file mode 100644 index 0000000..d1997ea --- /dev/null +++ b/src/utils/constants.tsx @@ -0,0 +1,19 @@ +export const CARD_NUMBER_DELIMITER = ' '; +export const EXPIRY_DATE_DELIMITER = '/'; +export const DEFAULT_CARD_NUMBER_PLACEHOLDER = '•••• •••• •••• ••••'; +export const DEFAULT_CARD_EXPIRY_DATE_PLACEHOLDER = 'MM/YY'; +export const DEFAULT_CVV_PLACEHOLDER = '•••'; + +export const SANDBOX_BASE_URL = 'https://api.sandbox.checkout.com'; +export const LIVE_BASE_URL = 'https://api.checkout.com'; +export const LIVE_LOGGER = + 'https://cloudevents.integration.checkout.com/logging'; +export const SANDBOX_LOGGER = + 'https://cloudevents.integration.sandbox.checkout.com/logging'; +export const MBC_LIVE_PUBLIC_KEY_REGEX = + /^pk_?(\w{8})-(\w{4})-(\w{4})-(\w{4})-(\w{12})$/; +export const NAS_LIVE_PUBLIC_KEY_REGEX = /^pk_?[a-z2-7]{26}[a-z2-7*#$=]$/; + +export const PUBLIC_KEY = 'pk_sbox_grxs6c67galutp6ze6ilz4obzym'; +export const DUMMY_CARD_NUMBER = '4242424242424242'; +export const DUMMY_CVV = '100'; diff --git a/src/utils/http.tsx b/src/utils/http.tsx new file mode 100644 index 0000000..887da72 --- /dev/null +++ b/src/utils/http.tsx @@ -0,0 +1,100 @@ +import { + FramesConfig, + FramesState, + GatewayBillingAddress, + TokenizationParams, +} from '../CardScreen/types/types'; +import { + EXPIRY_DATE_DELIMITER, + LIVE_BASE_URL, + MBC_LIVE_PUBLIC_KEY_REGEX, + NAS_LIVE_PUBLIC_KEY_REGEX, + SANDBOX_BASE_URL, +} from './constants'; +import {version} from '../../package.json'; + +export const getEnvironment = (key: string) => + MBC_LIVE_PUBLIC_KEY_REGEX.test(key) || NAS_LIVE_PUBLIC_KEY_REGEX.test(key) + ? `${LIVE_BASE_URL}/tokens` + : `${SANDBOX_BASE_URL}/tokens`; + +export const tokenize = async (e: TokenizationParams) => { + // eslint-disable-next-line no-useless-catch + try { + const response = await fetch(getEnvironment(e.key), { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + 'User-Agent': `frames-react-native/${version}`, + Authorization: e.key, + }, + body: JSON.stringify(e.body), + }); + + let json = await response.json(); + + if (response.ok) { + return json; + } else { + throw json; + } + } catch (error) { + throw error; + } +}; + +export const formatDataForTokenization = ( + state: FramesState, + config: FramesConfig, +): TokenizationParams => { + const number = state.cardNumber.replace(/[^A-Z0-9]+/gi, ''); + const expiry_month = state.expiryDate.split(EXPIRY_DATE_DELIMITER)[0]; + const expiry_year = `${new Date().getFullYear().toString().substring(0, 2)}${ + state.expiryDate.split(EXPIRY_DATE_DELIMITER)[1] + }`; + + let billing_address: GatewayBillingAddress = { + address_line1: '', + address_line2: '', + city: '', + state: '', + zip: '', + country: undefined, + }; + + if (config.cardholder?.billingAddress?.addressLine1) { + billing_address.address_line1 = + config.cardholder.billingAddress.addressLine1; + } + + if (config.cardholder?.billingAddress?.addressLine2) { + billing_address.address_line2 = + config.cardholder.billingAddress.addressLine2; + } + if (config.cardholder?.billingAddress) { + billing_address.city = config.cardholder.billingAddress.city || ''; + billing_address.state = config.cardholder.billingAddress.state || ''; + billing_address.zip = config.cardholder.billingAddress.zip || ''; + billing_address.country = config.cardholder.billingAddress.country; + } else { + // @ts-ignore + billing_address = null; + } + + return { + key: config.publicKey, + body: { + type: 'card', + number, + expiry_month, + expiry_year, + cvv: state.cvv, + name: config.cardholder?.name, + billing_address, + phone: { + number: config.cardholder?.phone, + }, + }, + }; +}; diff --git a/yarn.lock b/yarn.lock index db58c30..1c1f15f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -154,7 +154,7 @@ "@babel/traverse" "^7.24.7" "@babel/types" "^7.24.7" -"@babel/helper-module-imports@^7.24.7": +"@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== @@ -231,7 +231,7 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz#4d2d0f14820ede3b9807ea5fc36dfc8cd7da07f2" integrity sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg== -"@babel/helper-validator-identifier@^7.24.7": +"@babel/helper-validator-identifier@^7.19.1", "@babel/helper-validator-identifier@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== @@ -1132,7 +1132,7 @@ resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== -"@babel/runtime@^7.0.0", "@babel/runtime@^7.20.0", "@babel/runtime@^7.8.4": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.0", "@babel/runtime@^7.8.4": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== @@ -1178,6 +1178,103 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@emotion/babel-plugin@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" + integrity sha512-m4HEDZleaaCH+XgDDsPF15Ht6wTLsgDTeR3WYj9Q/k76JtWhrJjcP4+/XlG8LGT/Rol9qUfOIztXeA84ATpqPQ== + dependencies: + "@babel/helper-module-imports" "^7.16.7" + "@babel/runtime" "^7.18.3" + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/serialize" "^1.1.2" + babel-plugin-macros "^3.1.0" + convert-source-map "^1.5.0" + escape-string-regexp "^4.0.0" + find-root "^1.1.0" + source-map "^0.5.7" + stylis "4.2.0" + +"@emotion/cache@^11.11.0": + version "11.11.0" + resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.11.0.tgz#809b33ee6b1cb1a625fef7a45bc568ccd9b8f3ff" + integrity sha512-P34z9ssTCBi3e9EI1ZsWpNHcfY1r09ZO0rZbRO2ob3ZQMnFI35jB536qoXbkdesr5EUhYi22anuEJuyxifaqAQ== + dependencies: + "@emotion/memoize" "^0.8.1" + "@emotion/sheet" "^1.2.2" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + stylis "4.2.0" + +"@emotion/hash@^0.9.1": + version "0.9.1" + resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.9.1.tgz#4ffb0055f7ef676ebc3a5a91fb621393294e2f43" + integrity sha512-gJB6HLm5rYwSLI6PQa+X1t5CFGrv1J1TWG+sOyMCeKz2ojaj6Fnl/rZEspogG+cvqbt4AE/2eIyD2QfLKTBNlQ== + +"@emotion/memoize@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" + integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== + +"@emotion/react@^11.11.4": + version "11.11.4" + resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.4.tgz#3a829cac25c1f00e126408fab7f891f00ecc3c1d" + integrity sha512-t8AjMlF0gHpvvxk5mAtCqR4vmxiGHCeJBaQO6gncUSdklELOgtwjerNY2yuJNfwnc6vi16U/+uMF+afIawJ9iw== + dependencies: + "@babel/runtime" "^7.18.3" + "@emotion/babel-plugin" "^11.11.0" + "@emotion/cache" "^11.11.0" + "@emotion/serialize" "^1.1.3" + "@emotion/use-insertion-effect-with-fallbacks" "^1.0.1" + "@emotion/utils" "^1.2.1" + "@emotion/weak-memoize" "^0.3.1" + hoist-non-react-statics "^3.3.1" + +"@emotion/serialize@^1.1.2", "@emotion/serialize@^1.1.3": + version "1.1.4" + resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.4.tgz#fc8f6d80c492cfa08801d544a05331d1cc7cd451" + integrity sha512-RIN04MBT8g+FnDwgvIUi8czvr1LU1alUMI05LekWB5DGyTm8cCBMCRpq3GqaiyEDRptEXOyXnvZ58GZYu4kBxQ== + dependencies: + "@emotion/hash" "^0.9.1" + "@emotion/memoize" "^0.8.1" + "@emotion/unitless" "^0.8.1" + "@emotion/utils" "^1.2.1" + csstype "^3.0.2" + +"@emotion/sheet@^1.2.2": + version "1.2.2" + resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.2.2.tgz#d58e788ee27267a14342303e1abb3d508b6d0fec" + integrity sha512-0QBtGvaqtWi+nx6doRwDdBIzhNdZrXUppvTM4dtZZWEGTXL/XE/yJxLMGlDT1Gt+UHH5IX1n+jkXyytE/av7OA== + +"@emotion/unitless@^0.8.1": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.8.1.tgz#182b5a4704ef8ad91bde93f7a860a88fd92c79a3" + integrity sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ== + +"@emotion/use-insertion-effect-with-fallbacks@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.0.1.tgz#08de79f54eb3406f9daaf77c76e35313da963963" + integrity sha512-jT/qyKZ9rzLErtrjGgdkMBn2OP8wl0G3sQlBb3YPryvKHsjvINUhVaPFfP+fpBcOkmrVOVEEHQFJ7nbj2TH2gw== + +"@emotion/utils@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.2.1.tgz#bbab58465738d31ae4cb3dbb6fc00a5991f755e4" + integrity sha512-Y2tGf3I+XVnajdItskUCn6LX+VUDmP6lTL4fcqsXAv43dnlbZiuW4MWQW38rW/BVWSE7Q/7+XQocmpnRYILUmg== + +"@emotion/weak-memoize@^0.3.1": + version "0.3.1" + resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.1.tgz#d0fce5d07b0620caa282b5131c297bb60f9d87e6" + integrity sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww== + +"@es-joy/jsdoccomment@~0.46.0": + version "0.46.0" + resolved "https://registry.yarnpkg.com/@es-joy/jsdoccomment/-/jsdoccomment-0.46.0.tgz#47a2ee4bfc0081f252e058272dfab680aaed464d" + integrity sha512-C3Axuq1xd/9VqFZpW4YAzOx5O9q/LP46uIQy/iNDpHG3fmPa6TBtvfglMCs3RBiBxAIi0Go97r8+jvTt55XMyQ== + dependencies: + comment-parser "1.4.1" + esquery "^1.6.0" + jsdoc-type-pratt-parser "~4.0.0" + "@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" @@ -1222,6 +1319,11 @@ dependencies: "@hapi/hoek" "^9.0.0" +"@hookform/resolvers@^3.9.0": + version "3.9.0" + resolved "https://registry.yarnpkg.com/@hookform/resolvers/-/resolvers-3.9.0.tgz#cf540ac21c6c0cd24a40cf53d8e6d64391fb753d" + integrity sha512-bU0Gr4EepJ/EQsH/IwEzYLsT/PEj5C0ynLQ4m+GSHS+xKH4TfSelhluTgOaoc4kA5s7eCsQbM4wvZLzELmWzUg== + "@humanwhocodes/config-array@^0.11.14": version "0.11.14" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" @@ -1540,6 +1642,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@pkgr/core@^0.1.0": + version "0.1.1" + resolved "https://registry.yarnpkg.com/@pkgr/core/-/core-0.1.1.tgz#1ec17e2edbec25c8306d424ecfbf13c7de1aaa31" + integrity sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA== + "@react-native-community/cli-clean@13.6.9": version "13.6.9" resolved "https://registry.yarnpkg.com/@react-native-community/cli-clean/-/cli-clean-13.6.9.tgz#b6754f39c2b877c9d730feb848945150e1d52209" @@ -1995,6 +2102,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/json5@^0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" + integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== + "@types/node-forge@^1.3.0": version "1.3.11" resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-1.3.11.tgz#0972ea538ddb0f4d9c2fa0ec5db5724773a604da" @@ -2016,6 +2128,16 @@ dependencies: undici-types "~5.26.4" +"@types/normalize-package-data@^2.4.0": + version "2.4.4" + resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" + integrity sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA== + +"@types/parse-json@^4.0.0": + version "4.0.2" + resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.2.tgz#5950e50960793055845e956c427fc2b0d70c5239" + integrity sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw== + "@types/prop-types@*": version "15.7.12" resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" @@ -2297,6 +2419,11 @@ appdirsjs@^1.2.4: resolved "https://registry.yarnpkg.com/appdirsjs/-/appdirsjs-1.2.7.tgz#50b4b7948a26ba6090d4aede2ae2dc2b051be3b3" integrity sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw== +are-docs-informative@^0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/are-docs-informative/-/are-docs-informative-0.0.2.tgz#387f0e93f5d45280373d387a59d34c96db321963" + integrity sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig== + argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" @@ -2317,7 +2444,7 @@ array-buffer-byte-length@^1.0.1: call-bind "^1.0.5" is-array-buffer "^3.0.4" -array-includes@^3.1.6, array-includes@^3.1.8: +array-includes@^3.1.6, array-includes@^3.1.7, array-includes@^3.1.8: version "3.1.8" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.8.tgz#5e370cbe172fdd5dd6530c1d4aadda25281ba97d" integrity sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ== @@ -2346,7 +2473,19 @@ array.prototype.findlast@^1.2.5: es-object-atoms "^1.0.0" es-shim-unscopables "^1.0.2" -array.prototype.flat@^1.3.1: +array.prototype.findlastindex@^1.2.3: + version "1.2.5" + resolved "https://registry.yarnpkg.com/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz#8c35a755c72908719453f87145ca011e39334d0d" + integrity sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + es-errors "^1.3.0" + es-object-atoms "^1.0.0" + es-shim-unscopables "^1.0.2" + +array.prototype.flat@^1.3.1, array.prototype.flat@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz#1476217df8cff17d72ee8f3ba06738db5b387d18" integrity sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA== @@ -2469,6 +2608,15 @@ babel-plugin-jest-hoist@^29.6.3: "@types/babel__core" "^7.1.14" "@types/babel__traverse" "^7.0.6" +babel-plugin-macros@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1" + integrity sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg== + dependencies: + "@babel/runtime" "^7.12.5" + cosmiconfig "^7.0.0" + resolve "^1.19.0" + babel-plugin-polyfill-corejs2@^0.4.10: version "0.4.11" resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz#30320dfe3ffe1a336c15afdcdafd6fd615b25e33" @@ -2545,6 +2693,11 @@ bl@^4.1.0: inherits "^2.0.4" readable-stream "^3.4.0" +boolbase@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" + integrity sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww== + brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" @@ -2597,6 +2750,11 @@ buffer@^5.5.0: base64-js "^1.3.1" ieee754 "^1.1.13" +builtin-modules@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-3.3.0.tgz#cae62812b89801e9656336e46223e030386be7b6" + integrity sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw== + bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -2689,7 +2847,7 @@ ci-info@^2.0.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-2.0.0.tgz#67a9e964be31a51e15e5010d58e6f12834002f46" integrity sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ== -ci-info@^3.2.0: +ci-info@^3.2.0, ci-info@^3.4.0: version "3.9.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== @@ -2699,6 +2857,13 @@ cjs-module-lexer@^1.0.0: resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.3.1.tgz#c485341ae8fd999ca4ee5af2d7a1c9ae01e0099c" integrity sha512-a3KdPAANPbNE4ZUv9h6LckSl9zLsYOP4MBmhIPkRaeyybt+r4UghLvq+xw/YwUcC1gqylCkL4rdVs3Lwupjm4Q== +clean-regexp@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/clean-regexp/-/clean-regexp-1.0.0.tgz#8df7c7aae51fd36874e8f8d05b9180bc11a3fed7" + integrity sha512-GfisEZEJvzKrmGWkvfhgzcz/BllN1USeqD2V6tg14OAOgaCD2Z/PUEuxnAZ/nPvmaHRG7a8y77p1T/IRQ4D1Hw== + dependencies: + escape-string-regexp "^1.0.5" + cli-cursor@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-3.1.0.tgz#264305a7ae490d1d03bf0c9ba7c925d1753af307" @@ -2797,6 +2962,11 @@ commander@^9.4.1: resolved "https://registry.yarnpkg.com/commander/-/commander-9.5.0.tgz#bc08d1eb5cedf7ccb797a96199d41c7bc3e60d30" integrity sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ== +comment-parser@1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.4.1.tgz#bdafead37961ac079be11eb7ec65c4d021eaf9cc" + integrity sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -2837,6 +3007,11 @@ connect@^3.6.5: parseurl "~1.3.3" utils-merge "1.0.1" +convert-source-map@^1.5.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f" + integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A== + convert-source-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" @@ -2864,6 +3039,17 @@ cosmiconfig@^5.0.5, cosmiconfig@^5.1.0: js-yaml "^3.13.1" parse-json "^4.0.0" +cosmiconfig@^7.0.0: + version "7.1.0" + resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.1.0.tgz#1443b9afa596b670082ea46cbd8f6a62b84635f6" + integrity sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA== + dependencies: + "@types/parse-json" "^4.0.0" + import-fresh "^3.2.1" + parse-json "^5.0.0" + path-type "^4.0.0" + yaml "^1.10.0" + create-jest@^29.7.0: version "29.7.0" resolved "https://registry.yarnpkg.com/create-jest/-/create-jest-29.7.0.tgz#a355c5b3cb1e1af02ba177fe7afd7feee49a5320" @@ -2886,6 +3072,30 @@ cross-spawn@^7.0.2, cross-spawn@^7.0.3: shebang-command "^2.0.0" which "^2.0.1" +css-select@^5.1.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/css-select/-/css-select-5.1.0.tgz#b8ebd6554c3637ccc76688804ad3f6a6fdaea8a6" + integrity sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg== + dependencies: + boolbase "^1.0.0" + css-what "^6.1.0" + domhandler "^5.0.2" + domutils "^3.0.1" + nth-check "^2.0.1" + +css-tree@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d" + integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q== + dependencies: + mdn-data "2.0.14" + source-map "^0.6.1" + +css-what@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.1.0.tgz#fb5effcf76f1ddea2c81bdfaa4de44e79bac70f4" + integrity sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw== + csstype@^3.0.2: version "3.1.3" resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.3.tgz#d80ff294d114fb0e6ac500fbf85b60137d7eff81" @@ -2930,7 +3140,14 @@ debug@2.6.9, debug@^2.2.0, debug@^2.6.9: dependencies: ms "2.0.0" -debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4: +debug@^3.2.7: + version "3.2.7" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a" + integrity sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.3.5: version "4.3.5" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== @@ -3028,6 +3245,36 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -3053,6 +3300,11 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== +entities@^4.2.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + envinfo@^7.10.0: version "7.13.0" resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.13.0.tgz#81fbb81e5da35d74e814941aeab7c325a606fb31" @@ -3164,6 +3416,11 @@ es-iterator-helpers@^1.0.19: iterator.prototype "^1.1.2" safe-array-concat "^1.1.2" +es-module-lexer@^1.5.3: + version "1.5.4" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" + integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== + es-object-atoms@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/es-object-atoms/-/es-object-atoms-1.0.0.tgz#ddb55cd47ac2e240701260bc2a8e31ecb643d941" @@ -3221,11 +3478,35 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -eslint-config-prettier@^8.5.0: +eslint-config-prettier@^8.10.0, eslint-config-prettier@^8.5.0: version "8.10.0" resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11" integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg== +eslint-import-resolver-node@^0.3.9: + version "0.3.9" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz#d4eaac52b8a2e7c3cd1903eb00f7e053356118ac" + integrity sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g== + dependencies: + debug "^3.2.7" + is-core-module "^2.13.0" + resolve "^1.22.4" + +eslint-module-utils@^2.8.0: + version "2.8.1" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz#52f2404300c3bd33deece9d7372fb337cc1d7c34" + integrity sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q== + dependencies: + debug "^3.2.7" + +eslint-plugin-consistent-default-export-name@^0.0.15: + version "0.0.15" + resolved "https://registry.yarnpkg.com/eslint-plugin-consistent-default-export-name/-/eslint-plugin-consistent-default-export-name-0.0.15.tgz#c2050daf34c96c833cd9b40785cfa64953e8dc3c" + integrity sha512-gqW7dnJbWMxI5H6/Pyz6Sl/vBMwOktePMI2iuuKPb4N82uvemUkfaWhsRZCKndSzpIVaCZ9wdspCVO1tm0wXJQ== + dependencies: + lodash "^4.17.21" + pkg-dir "^5.0.0" + eslint-plugin-eslint-comments@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.2.0.tgz#9e1cd7b4413526abb313933071d7aba05ca12ffa" @@ -3242,6 +3523,29 @@ eslint-plugin-ft-flow@^2.0.1: lodash "^4.17.21" string-natural-compare "^3.0.1" +eslint-plugin-import@^2.29.1: + version "2.29.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz#d45b37b5ef5901d639c15270d74d46d161150643" + integrity sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw== + dependencies: + array-includes "^3.1.7" + array.prototype.findlastindex "^1.2.3" + array.prototype.flat "^1.3.2" + array.prototype.flatmap "^1.3.2" + debug "^3.2.7" + doctrine "^2.1.0" + eslint-import-resolver-node "^0.3.9" + eslint-module-utils "^2.8.0" + hasown "^2.0.0" + is-core-module "^2.13.1" + is-glob "^4.0.3" + minimatch "^3.1.2" + object.fromentries "^2.0.7" + object.groupby "^1.0.1" + object.values "^1.1.7" + semver "^6.3.1" + tsconfig-paths "^3.15.0" + eslint-plugin-jest@^27.9.0: version "27.9.0" resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.9.0.tgz#7c98a33605e1d8b8442ace092b60e9919730000b" @@ -3249,6 +3553,22 @@ eslint-plugin-jest@^27.9.0: dependencies: "@typescript-eslint/utils" "^5.10.0" +eslint-plugin-jsdoc@^48.2.12: + version "48.7.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.7.0.tgz#1450e5bccc320dee01a8d24ac71651a539857692" + integrity sha512-5oiVf7Y+ZxGYQTlLq81X72n+S+hjvS/u0upAdbpPEeaIZILK3MKN8lm/6QqKioBjm/qZ0B5XpMQUtc2fUkqXAg== + dependencies: + "@es-joy/jsdoccomment" "~0.46.0" + are-docs-informative "^0.0.2" + comment-parser "1.4.1" + debug "^4.3.5" + escape-string-regexp "^4.0.0" + esquery "^1.6.0" + parse-imports "^2.1.1" + semver "^7.6.2" + spdx-expression-parse "^4.0.0" + synckit "^0.9.0" + eslint-plugin-prettier@^4.2.1: version "4.2.1" resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-4.2.1.tgz#651cbb88b1dab98bfd42f017a12fa6b2d993f94b" @@ -3266,14 +3586,14 @@ eslint-plugin-react-native-globals@^0.1.1: resolved "https://registry.yarnpkg.com/eslint-plugin-react-native-globals/-/eslint-plugin-react-native-globals-0.1.2.tgz#ee1348bc2ceb912303ce6bdbd22e2f045ea86ea2" integrity sha512-9aEPf1JEpiTjcFAmmyw8eiIXmcNZOqaZyHO77wgm0/dWfT/oxC1SrIq8ET38pMxHYrcB6Uew+TzUVsBeczF88g== -eslint-plugin-react-native@^4.0.0: +eslint-plugin-react-native@^4.0.0, eslint-plugin-react-native@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/eslint-plugin-react-native/-/eslint-plugin-react-native-4.1.0.tgz#5343acd3b2246bc1b857ac38be708f070d18809f" integrity sha512-QLo7rzTBOl43FvVqDdq5Ql9IoElIuTdjrz9SKAXCvULvBoRZ44JGSkx9z4999ZusCsb4rK3gjS8gOGyeYqZv2Q== dependencies: eslint-plugin-react-native-globals "^0.1.1" -eslint-plugin-react@^7.30.1: +eslint-plugin-react@^7.30.1, eslint-plugin-react@^7.33.2: version "7.34.3" resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.34.3.tgz#9965f27bd1250a787b5d4cfcc765e5a5d58dcb7b" integrity sha512-aoW4MV891jkUulwDApQbPYTVZmeuSyFrudpbTAQuj5Fv8VL+o6df2xIGpw8B0hPjAaih1/Fb0om9grCdyFYemA== @@ -3297,6 +3617,26 @@ eslint-plugin-react@^7.30.1: semver "^6.3.1" string.prototype.matchall "^4.0.11" +eslint-plugin-unicorn@^44.0.2: + version "44.0.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-unicorn/-/eslint-plugin-unicorn-44.0.2.tgz#6324a001c0a5e2ac00fb51b30db27d14c6c36ab3" + integrity sha512-GLIDX1wmeEqpGaKcnMcqRvMVsoabeF0Ton0EX4Th5u6Kmf7RM9WBl705AXFEsns56ESkEs0uyelLuUTvz9Tr0w== + dependencies: + "@babel/helper-validator-identifier" "^7.19.1" + ci-info "^3.4.0" + clean-regexp "^1.0.0" + eslint-utils "^3.0.0" + esquery "^1.4.0" + indent-string "^4.0.0" + is-builtin-module "^3.2.0" + lodash "^4.17.21" + pluralize "^8.0.0" + read-pkg-up "^7.0.1" + regexp-tree "^0.1.24" + safe-regex "^2.1.1" + semver "^7.3.7" + strip-indent "^3.0.0" + eslint-scope@5.1.1, eslint-scope@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" @@ -3313,7 +3653,14 @@ eslint-scope@^7.2.2: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^2.1.0: +eslint-utils@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" + integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== + dependencies: + eslint-visitor-keys "^2.0.0" + +eslint-visitor-keys@^2.0.0, eslint-visitor-keys@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== @@ -3381,7 +3728,7 @@ esprima@^4.0.0, esprima@~4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.4.2: +esquery@^1.4.0, esquery@^1.4.2, esquery@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== @@ -3539,6 +3886,11 @@ find-cache-dir@^2.0.0: make-dir "^2.0.0" pkg-dir "^3.0.0" +find-root@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4" + integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng== + find-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" @@ -3829,6 +4181,18 @@ hermes-profile-transformer@^0.0.6: dependencies: source-map "^0.7.3" +hoist-non-react-statics@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz#ece0acaf71d62c2969c2ec59feff42a4b1a85b45" + integrity sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw== + dependencies: + react-is "^16.7.0" + +hosted-git-info@^2.1.4: + version "2.8.9" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9" + integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw== + html-escaper@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453" @@ -3896,6 +4260,11 @@ imurmurhash@^0.1.4: resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA== +indent-string@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251" + integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg== + inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" @@ -3960,12 +4329,19 @@ is-boolean-object@^1.1.0: call-bind "^1.0.2" has-tostringtag "^1.0.0" +is-builtin-module@^3.2.0: + version "3.2.1" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-3.2.1.tgz#f03271717d8654cfcaf07ab0463faa3571581169" + integrity sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A== + dependencies: + builtin-modules "^3.3.0" + is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055" integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA== -is-core-module@^2.13.0: +is-core-module@^2.13.0, is-core-module@^2.13.1: version "2.14.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.14.0.tgz#43b8ef9f46a6a08888db67b1ffd4ec9e3dfd59d1" integrity sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A== @@ -4667,6 +5043,11 @@ jscodeshift@^0.14.0: temp "^0.8.4" write-file-atomic "^2.3.0" +jsdoc-type-pratt-parser@~4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz#136f0571a99c184d84ec84662c45c29ceff71114" + integrity sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ== + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -4702,6 +5083,13 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw== +json5@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.2.tgz#63d98d60f21b313b77c4d6da18bfa69d80e1d593" + integrity sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA== + dependencies: + minimist "^1.2.0" + json5@^2.2.3: version "2.2.3" resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" @@ -4867,6 +5255,11 @@ marky@^1.2.2: resolved "https://registry.yarnpkg.com/marky/-/marky-1.2.5.tgz#55796b688cbd72390d2d399eaaf1832c9413e3c0" integrity sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q== +mdn-data@2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50" + integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow== + memoize-one@^5.0.0: version "5.2.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" @@ -5101,6 +5494,11 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== +min-indent@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/min-indent/-/min-indent-1.0.1.tgz#a63f681673b30571fbe8bc25686ae746eefa9869" + integrity sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg== + minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -5115,7 +5513,7 @@ minimatch@^9.0.4: dependencies: brace-expansion "^2.0.1" -minimist@^1.2.6: +minimist@^1.2.0, minimist@^1.2.6: version "1.2.8" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== @@ -5142,7 +5540,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: +ms@2.1.3, ms@^2.1.1: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -5206,6 +5604,16 @@ node-stream-zip@^1.9.1: resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea" integrity sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw== +normalize-package-data@^2.5.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" + integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA== + dependencies: + hosted-git-info "^2.1.4" + resolve "^1.10.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + normalize-path@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" @@ -5218,6 +5626,13 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" +nth-check@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/nth-check/-/nth-check-2.1.1.tgz#c9eab428effce36cd6b92c924bdb000ef1f1ed1d" + integrity sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w== + dependencies: + boolbase "^1.0.0" + nullthrows@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/nullthrows/-/nullthrows-1.1.1.tgz#7818258843856ae971eae4208ad7d7eb19a431b1" @@ -5262,7 +5677,7 @@ object.entries@^1.1.8: define-properties "^1.2.1" es-object-atoms "^1.0.0" -object.fromentries@^2.0.8: +object.fromentries@^2.0.7, object.fromentries@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.8.tgz#f7195d8a9b97bd95cbc1999ea939ecd1a2b00c65" integrity sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ== @@ -5272,6 +5687,15 @@ object.fromentries@^2.0.8: es-abstract "^1.23.2" es-object-atoms "^1.0.0" +object.groupby@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/object.groupby/-/object.groupby-1.0.3.tgz#9b125c36238129f6f7b61954a1e7176148d5002e" + integrity sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ== + dependencies: + call-bind "^1.0.7" + define-properties "^1.2.1" + es-abstract "^1.23.2" + object.hasown@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/object.hasown/-/object.hasown-1.1.4.tgz#e270ae377e4c120cdcb7656ce66884a6218283dc" @@ -5281,7 +5705,7 @@ object.hasown@^1.1.4: es-abstract "^1.23.2" es-object-atoms "^1.0.0" -object.values@^1.1.6, object.values@^1.2.0: +object.values@^1.1.6, object.values@^1.1.7, object.values@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.2.0.tgz#65405a9d92cee68ac2d303002e0b8470a4d9ab1b" integrity sha512-yBYjY9QX2hnRmZHAjG/f13MzmBzxzYgQhFrke06TTyKY5zSTEqkOeukBzIdVA3j3ulu8Qa3MbVFShV7T2RmGtQ== @@ -5412,6 +5836,14 @@ parent-module@^1.0.0: dependencies: callsites "^3.0.0" +parse-imports@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/parse-imports/-/parse-imports-2.1.1.tgz#ce52141df24990065d72a446a364bffd595577f4" + integrity sha512-TDT4HqzUiTMO1wJRwg/t/hYk8Wdp3iF/ToMIlAoVQfL1Xs/sTxq1dKWSMjMbQmIarfWKymOyly40+zmPHXMqCA== + dependencies: + es-module-lexer "^1.5.3" + slashes "^3.0.12" + parse-json@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0" @@ -5420,7 +5852,7 @@ parse-json@^4.0.0: error-ex "^1.3.1" json-parse-better-errors "^1.0.1" -parse-json@^5.2.0: +parse-json@^5.0.0, parse-json@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd" integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg== @@ -5499,6 +5931,18 @@ pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" +pkg-dir@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-5.0.0.tgz#a02d6aebe6ba133a928f74aec20bafdfe6b8e760" + integrity sha512-NPE8TDbzl/3YQYY7CSS228s3g2ollTFnc+Qi3tqmqJp9Vg2ovUpixcJEo2HJScN2Ez+kEaal6y70c0ehqJBJeA== + dependencies: + find-up "^5.0.0" + +pluralize@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-8.0.0.tgz#1a6fa16a38d12a1901e0320fa017051c539ce3b1" + integrity sha512-Nc3IT5yHzflTfbjgqWcCPpo7DaKy4FnpB0l/zCAW0Tc7jxAiuqSxHasntB3D7887LSrA93kDJ9IXovxJYxyLCA== + possible-typed-array-names@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz#89bb63c6fada2c3e90adc4a647beeeb39cc7bf8f" @@ -5569,6 +6013,11 @@ prop-types@^15.8.1: object-assign "^4.1.1" react-is "^16.13.1" +property-expr@^2.0.5: + version "2.0.6" + resolved "https://registry.yarnpkg.com/property-expr/-/property-expr-2.0.6.tgz#f77bc00d5928a6c748414ad12882e83f24aec1e8" + integrity sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA== + punycode@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" @@ -5609,12 +6058,17 @@ react-devtools-core@^5.0.0: shell-quote "^1.6.1" ws "^7" +react-hook-form@^7.52.1: + version "7.52.1" + resolved "https://registry.yarnpkg.com/react-hook-form/-/react-hook-form-7.52.1.tgz#ec2c96437b977f8b89ae2d541a70736c66284852" + integrity sha512-uNKIhaoICJ5KQALYZ4TOaOLElyM+xipord+Ha3crEFhTntdLvWZqVY49Wqd/0GiVCA/f9NjemLeiNPjG7Hpurg== + "react-is@^16.12.0 || ^17.0.0 || ^18.0.0", react-is@^18.0.0, react-is@^18.2.0: version "18.3.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.3.1.tgz#e83557dc12eae63a99e003a46388b1dcbb44db7e" integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg== -react-is@^16.13.1: +react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== @@ -5624,6 +6078,20 @@ react-is@^17.0.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-native-animation-hooks@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/react-native-animation-hooks/-/react-native-animation-hooks-1.0.1.tgz#fcda58867d708084cc63c6fcd001f5375a40f45b" + integrity sha512-s1o71m9sPpEYwFpHTdBouO50jzvyxIVB4MmNwfVGgPWkdL02zKOzyXcNuPJmoCuLIstUj5TBEwtnGk1WMFJx5A== + +react-native-svg@^15.4.0: + version "15.4.0" + resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-15.4.0.tgz#a4929deece1f438282e18d007b12af5e4f718fda" + integrity sha512-zkBEbme/Dba4yqreg/oI2P6/6LrLywWY7HhaSwpU7Pb5COpTd2fV6/ShsgZz8GRFFdidUPwWmx01FITUsjhkmw== + dependencies: + css-select "^5.1.0" + css-tree "^1.1.3" + warn-once "0.1.1" + react-native@0.74.3: version "0.74.3" resolved "https://registry.yarnpkg.com/react-native/-/react-native-0.74.3.tgz#eef32cd10afb1f4b26f75b79eefd6b220c63953c" @@ -5696,6 +6164,25 @@ react@18.2.0: dependencies: loose-envify "^1.1.0" +read-pkg-up@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-7.0.1.tgz#f3a6135758459733ae2b95638056e1854e7ef507" + integrity sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg== + dependencies: + find-up "^4.1.0" + read-pkg "^5.2.0" + type-fest "^0.8.1" + +read-pkg@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-5.2.0.tgz#7bf295438ca5a33e56cd30e053b34ee7250c93cc" + integrity sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg== + dependencies: + "@types/normalize-package-data" "^2.4.0" + normalize-package-data "^2.5.0" + parse-json "^5.0.0" + type-fest "^0.6.0" + readable-stream@^3.4.0: version "3.6.2" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967" @@ -5775,6 +6262,11 @@ regenerator-transform@^0.15.2: dependencies: "@babel/runtime" "^7.8.4" +regexp-tree@^0.1.24, regexp-tree@~0.1.1: + version "0.1.27" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.27.tgz#2198f0ef54518ffa743fe74d983b56ffd631b6cd" + integrity sha512-iETxpjK6YoRWJG5o6hXLwvjYAoW+FEZn9os0PD/b6AP6xQwsa/Y7lCVgIixBbUPMfhu+i2LtdeAqVTgGlQarfA== + regexp.prototype.flags@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.2.tgz#138f644a3350f981a858c44f6bb1a61ff59be334" @@ -5841,7 +6333,7 @@ resolve.exports@^2.0.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== -resolve@^1.14.2, resolve@^1.20.0: +resolve@^1.10.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.4: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -5922,6 +6414,13 @@ safe-regex-test@^1.0.3: es-errors "^1.3.0" is-regex "^1.1.4" +safe-regex@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-2.1.1.tgz#f7128f00d056e2fe5c11e81a1324dd974aadced2" + integrity sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A== + dependencies: + regexp-tree "~0.1.1" + scheduler@0.24.0-canary-efb381bbf-20230505: version "0.24.0-canary-efb381bbf-20230505" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.24.0-canary-efb381bbf-20230505.tgz#5dddc60e29f91cd7f8b983d7ce4a99c2202d178f" @@ -5944,7 +6443,7 @@ selfsigned@^2.4.1: "@types/node-forge" "^1.3.0" node-forge "^1" -semver@^5.6.0: +"semver@2 || 3 || 4 || 5", semver@^5.6.0: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== @@ -5954,7 +6453,7 @@ semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0: +semver@^7.3.7, semver@^7.5.2, semver@^7.5.3, semver@^7.5.4, semver@^7.6.0, semver@^7.6.2: version "7.6.2" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.2.tgz#1e3b34759f896e8f14d6134732ce798aeb0c6e13" integrity sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w== @@ -6074,6 +6573,11 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +slashes@^3.0.12: + version "3.0.12" + resolved "https://registry.yarnpkg.com/slashes/-/slashes-3.0.12.tgz#3d664c877ad542dc1509eaf2c50f38d483a6435a" + integrity sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA== + slice-ansi@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-2.1.0.tgz#cacd7693461a637a5788d92a7dd4fba068e81636" @@ -6099,7 +6603,7 @@ source-map-support@^0.5.16, source-map-support@~0.5.20: buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.5.6: +source-map@^0.5.6, source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== @@ -6114,6 +6618,40 @@ source-map@^0.7.3: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== +spdx-correct@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c" + integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids "^3.0.0" + +spdx-exceptions@^2.1.0: + version "2.5.0" + resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz#5d607d27fc806f66d7b64a766650fa890f04ed66" + integrity sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w== + +spdx-expression-parse@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679" + integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-expression-parse@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz#a23af9f3132115465dac215c099303e4ceac5794" + integrity sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ== + dependencies: + spdx-exceptions "^2.1.0" + spdx-license-ids "^3.0.0" + +spdx-license-ids@^3.0.0: + version "3.0.18" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz#22aa922dcf2f2885a6494a261f2d8b75345d0326" + integrity sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -6244,6 +6782,11 @@ strip-ansi@^6.0.0, strip-ansi@^6.0.1: dependencies: ansi-regex "^5.0.1" +strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA== + strip-bom@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" @@ -6254,6 +6797,13 @@ strip-final-newline@^2.0.0: resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== +strip-indent@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-indent/-/strip-indent-3.0.0.tgz#c32e1cee940b6b3432c771bc2c54bcce73cd3001" + integrity sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ== + dependencies: + min-indent "^1.0.0" + strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -6264,6 +6814,11 @@ strnum@^1.0.5: resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== +stylis@4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.2.0.tgz#79daee0208964c8fe695a42fcffcac633a211a51" + integrity sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw== + sudo-prompt@^9.0.0: version "9.2.1" resolved "https://registry.yarnpkg.com/sudo-prompt/-/sudo-prompt-9.2.1.tgz#77efb84309c9ca489527a4e749f287e6bdd52afd" @@ -6295,6 +6850,14 @@ supports-preserve-symlinks-flag@^1.0.0: resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== +synckit@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/synckit/-/synckit-0.9.0.tgz#5b33b458b3775e4466a5b377fba69c63572ae449" + integrity sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg== + dependencies: + "@pkgr/core" "^0.1.0" + tslib "^2.6.2" + temp-dir@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/temp-dir/-/temp-dir-2.0.0.tgz#bde92b05bdfeb1516e804c9c00ad45177f31321e" @@ -6344,6 +6907,11 @@ through2@^2.0.1: readable-stream "~2.3.6" xtend "~4.0.1" +tiny-case@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tiny-case/-/tiny-case-1.0.3.tgz#d980d66bc72b5d5a9ca86fb7c9ffdb9c898ddd03" + integrity sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q== + tmpl@1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc" @@ -6366,6 +6934,11 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== +toposort@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/toposort/-/toposort-2.0.2.tgz#ae21768175d1559d48bef35420b2f4962f09c330" + integrity sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg== + tr46@~0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" @@ -6376,12 +6949,22 @@ ts-api-utils@^1.3.0: resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== +tsconfig-paths@^3.15.0: + version "3.15.0" + resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz#5299ec605e55b1abb23ec939ef15edaf483070d4" + integrity sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg== + dependencies: + "@types/json5" "^0.0.29" + json5 "^1.0.2" + minimist "^1.2.6" + strip-bom "^3.0.0" + tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.1: +tslib@^2.0.1, tslib@^2.6.2: version "2.6.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== @@ -6415,11 +6998,26 @@ type-fest@^0.21.3: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w== +type-fest@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b" + integrity sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg== + type-fest@^0.7.1: version "0.7.1" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.7.1.tgz#8dda65feaf03ed78f0a3f9678f1869147f7c5c48" integrity sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg== +type-fest@^0.8.1: + version "0.8.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" + integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== + +type-fest@^2.19.0: + version "2.19.0" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-2.19.0.tgz#88068015bb33036a598b952e55e9311a60fd3a9b" + integrity sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA== + typed-array-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" @@ -6551,6 +7149,14 @@ v8-to-istanbul@^9.0.1: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^2.0.0" +validate-npm-package-license@^3.0.1: + version "3.0.4" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" + integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew== + dependencies: + spdx-correct "^3.0.0" + spdx-expression-parse "^3.0.0" + vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" @@ -6568,6 +7174,11 @@ walker@^1.0.7, walker@^1.0.8: dependencies: makeerror "1.0.12" +warn-once@0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/warn-once/-/warn-once-0.1.1.tgz#952088f4fb56896e73fd4e6a3767272a3fccce43" + integrity sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q== + wcwidth@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" @@ -6732,6 +7343,11 @@ yallist@^3.0.2: resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== +yaml@^1.10.0: + version "1.10.2" + resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" + integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== + yaml@^2.2.1: version "2.4.5" resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.4.5.tgz#60630b206dd6d84df97003d33fc1ddf6296cca5e" @@ -6784,3 +7400,13 @@ yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== + +yup@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/yup/-/yup-1.4.0.tgz#898dcd660f9fb97c41f181839d3d65c3ee15a43e" + integrity sha512-wPbgkJRCqIf+OHyiTBQoJiP5PFuAXaWiJK6AmYkzQAh5/c2K9hzSApBZG5wV9KoKSePF7sAxmNSvh/13YHkFDg== + dependencies: + property-expr "^2.0.5" + tiny-case "^1.0.3" + toposort "^2.0.2" + type-fest "^2.19.0"