diff --git a/HELPERS-FIX.md b/HELPERS-FIX.md new file mode 100644 index 0000000000..96b56ea58e --- /dev/null +++ b/HELPERS-FIX.md @@ -0,0 +1,56 @@ +# React Native UI Lib - Helpers Fix + +## Problem +The `react-native-ui-lib` package was missing the compiled JavaScript files for the `src/helpers` directory, causing Metro bundler to fail with: + +``` +error Unable to resolve module ./helpers from /path/to/node_modules/react-native-ui-lib/src/index.ts +``` + +## Solution +This fix ensures that the TypeScript files in `src/helpers/` are properly transpiled to JavaScript and made available for Metro bundler consumption. + +## What was done: + +1. **Added a build script** (`scripts/build/buildHelpers.js`) that: + - Transpiles the `src/helpers/` TypeScript files to JavaScript + - Transpiles the main `src/index.ts` to `src/index.js` + - Fixes import extensions for Metro bundler compatibility + - Updates `package.json` to point to the JavaScript entry point + +2. **Updated package.json** to include the new build script: + ```json + { + "scripts": { + "build:helpers": "node scripts/build/buildHelpers.js" + } + } + ``` + +3. **Generated missing files**: + - `src/helpers/index.js` + - `src/helpers/AvatarHelper.js` + - `src/helpers/Profiler.js` + - `src/helpers/FormattingPresenter.js` + - `src/index.js` + +## Usage + +### For library maintainers: +Run this whenever you make changes to the helpers: +```bash +npm run build:helpers +``` + +### For users experiencing the build error: +If you're using this library as a git dependency and encountering the helpers import error, you can fix it by running: + +```bash +cd node_modules/react-native-ui-lib +npm run build:helpers +``` + +Or clone the fixed version of this repository. + +## Long-term solution: +The build process should be updated to automatically include these transpilation steps in the main build pipeline to prevent this issue from occurring in future releases. diff --git a/INTEGRATION-INSTRUCTIONS.md b/INTEGRATION-INSTRUCTIONS.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/UPDATED-PACKAGE.json b/UPDATED-PACKAGE.json new file mode 100644 index 0000000000..e69de29bb2 diff --git a/android/app/build.gradle b/android/app/build.gradle index b16e3ae4c0..4219a0740f 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -1,63 +1,16 @@ apply plugin: "com.android.application" apply plugin: "org.jetbrains.kotlin.android" -apply plugin: "com.facebook.react" - - -// project.ext.react = [ -// entryFile: "index.js", -// enableHermes: true, // clean and rebuild if changing -// ] - -/* This is the configuration block to customize your React Native Android app. - * By default you don't need to apply any configuration, just uncomment the lines you need. - */ -react { - /* Folders */ - // The root of your project, i.e. where "package.json" lives. Default is '..' - root = file("../../") - // The folder where the react-native NPM package is. Default is ../node_modules/react-native - reactNativeDir = file("../../node_modules/react-native") - // The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen - // codegenDir = file("../node_modules/@react-native/codegen") - // The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js - // cliFile = file("../node_modules/react-native/cli.js") - /* Variants */ - // The list of variants to that are debuggable. For those we're going to - // skip the bundling of the JS bundle and the assets. By default is just 'debug'. - // If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants. - // debuggableVariants = ["liteDebug", "prodDebug"] - /* Bundling */ - // A list containing the node command and its flags. Default is just 'node'. - // nodeExecutableAndArgs = ["node"] - // - // The command to run when bundling. By default is 'bundle' - // bundleCommand = "ram-bundle" - // - // The path to the CLI configuration file. Default is empty. - // bundleConfig = file(../rn-cli.config.js) - // - // The name of the generated asset file containing your JS bundle - // bundleAssetName = "MyApplication.android.bundle" - // - // The entry file for bundle generation. Default is 'index.android.js' or 'index.js' - entryFile = file("index.js") - // - // A list of extra flags to pass to the 'bundle' commands. - // See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle - // extraPackagerArgs = [] - /* Hermes Commands */ - // The hermes compiler command to run. By default it is 'hermesc' - // hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc" - // - // The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map" - // hermesFlags = ["-O", "-output-source-map"] - autolinkLibrariesWithApp() -} /** * Set this to true to Run Proguard on Release builds to minify the Java bytecode. */ def enableProguardInReleaseBuilds = false + +/** + * Set this to true to enable Hermes JS Engine + */ +def hermesEnabled = true + /** * The preferred build flavor of JavaScriptCore (JSC) * @@ -78,6 +31,10 @@ android { compileSdk rootProject.ext.compileSdkVersion namespace "com.rnuilib" + + buildFeatures { + buildConfig true + } defaultConfig { applicationId "com.rnuilib" @@ -85,7 +42,9 @@ android { targetSdkVersion rootProject.ext.targetSdkVersion versionCode 1 versionName "1.0" - multiDexEnabled true + + buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", "false" + buildConfigField "boolean", "IS_HERMES_ENABLED", "true" } compileOptions { @@ -120,19 +79,30 @@ android { } dependencies { - // The version of react-native is set by the React Native Gradle Plugin - implementation("com.facebook.react:react-android") - implementation("com.facebook.react:flipper-integration:0.73.9") + // React Native dependencies with specific versions for 0.79 + implementation("com.facebook.react:react-android:0.79.0") + // Flipper is deprecated in RN 0.79+ + // implementation("com.facebook.react:flipper-integration:0.79.0") - implementation project(':react-native-navigation') implementation 'com.facebook.fresco:fresco:2.5.0' implementation 'com.facebook.fresco:animated-gif:2.5.0' if (hermesEnabled.toBoolean()) { - implementation("com.facebook.react:hermes-android") + implementation("com.facebook.react:hermes-android:0.79.0") } else { implementation jscFlavor } } -// apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project) +// For React Native 0.76, try to use the autolinking system if available +def autolinkedLibraries = [] +try { + apply from: file("../../node_modules/react-native/scripts/autolinking/autolinking.gradle") + autolinkedLibraries = getReactNativeLibraries() +} catch (Exception ignored) { + // Fallback if autolinking fails +} + +android.buildTypes.all { buildType -> + buildType.resValue 'string', "rn_config_reader_custom_package", '""' +} diff --git a/android/app/src/main/java/com/rnuilib/MainActivity.kt b/android/app/src/main/java/com/rnuilib/MainActivity.kt index c563a51b51..d9e5845e67 100644 --- a/android/app/src/main/java/com/rnuilib/MainActivity.kt +++ b/android/app/src/main/java/com/rnuilib/MainActivity.kt @@ -1,11 +1,22 @@ -package com.rnuilib; +package com.rnuilib import com.facebook.react.ReactActivity import com.facebook.react.ReactActivityDelegate import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.fabricEnabled import com.facebook.react.defaults.DefaultReactActivityDelegate -import com.reactnativenavigation.NavigationActivity -class MainActivity : NavigationActivity() { +class MainActivity : ReactActivity() { + /** + * Returns the name of the main component registered from JavaScript. This is used to schedule + * rendering of the component. + */ + override fun getMainComponentName(): String = "rnuilib" + + /** + * Returns the instance of the [ReactActivityDelegate]. We use [DefaultReactActivityDelegate] + * which allows you to enable New Architecture with a single boolean flags [fabricEnabled] + */ + override fun createReactActivityDelegate(): ReactActivityDelegate = + DefaultReactActivityDelegate(this, mainComponentName, fabricEnabled) } \ No newline at end of file diff --git a/android/app/src/main/java/com/rnuilib/MainApplication.kt b/android/app/src/main/java/com/rnuilib/MainApplication.kt index dd6c15fb69..03216113cd 100644 --- a/android/app/src/main/java/com/rnuilib/MainApplication.kt +++ b/android/app/src/main/java/com/rnuilib/MainApplication.kt @@ -1,7 +1,6 @@ -package com.rnuilib; +package com.rnuilib import android.app.Application -import com.facebook.react.PackageList import com.facebook.react.ReactApplication import com.facebook.react.ReactHost import com.facebook.react.ReactNativeHost @@ -9,41 +8,41 @@ import com.facebook.react.ReactPackage import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint.load import com.facebook.react.defaults.DefaultReactHost.getDefaultReactHost import com.facebook.react.defaults.DefaultReactNativeHost -import com.facebook.react.flipper.ReactNativeFlipper +import com.facebook.react.shell.MainReactPackage import com.facebook.soloader.SoLoader -import com.reactnativenavigation.NavigationApplication -import com.wix.reactnativeuilib.UiLibPackageList; -class MainApplication : NavigationApplication() { +class MainApplication : Application(), ReactApplication { override val reactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(this) { - override fun getPackages(): List = - PackageList(this).packages.apply { - // Packages that cannot be autolinked yet can be added manually here, for example: -// add(UiLibPackageList(MainApplication.this).getPackageList()) -// addAll(UiLibPackageList(this@MainApplication).getPackageList()) - // add(MyReactNativePackage()) - } + override fun getPackages(): List { + // Create a list of packages manually + val packages = mutableListOf() + packages.add(MainReactPackage()) + + // Add other packages manually as needed + // packages.add(YourOtherPackage()) + + return packages + } override fun getJSMainModuleName(): String = "index" - override fun getUseDeveloperSupport(): Boolean = BuildConfig.DEBUG + override fun getUseDeveloperSupport(): Boolean = com.rnuilib.BuildConfig.DEBUG - override val isNewArchEnabled: Boolean = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED - override val isHermesEnabled: Boolean = BuildConfig.IS_HERMES_ENABLED + override val isNewArchEnabled: Boolean = com.rnuilib.BuildConfig.IS_NEW_ARCHITECTURE_ENABLED + override val isHermesEnabled: Boolean = com.rnuilib.BuildConfig.IS_HERMES_ENABLED } override val reactHost: ReactHost - get() = getDefaultReactHost(this.applicationContext, reactNativeHost) + get() = getDefaultReactHost(applicationContext, reactNativeHost) override fun onCreate() { super.onCreate() SoLoader.init(this, false) - if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { + if (com.rnuilib.BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { // If you opted-in for the New Architecture, we load the native entry point for this app. load() } - ReactNativeFlipper.initializeFlipper(this, reactNativeHost.reactInstanceManager) } } diff --git a/android/app/src/main/java/com/rnuilib/MainApplicationReactNativeHost.java b/android/app/src/main/java/com/rnuilib/MainApplicationReactNativeHost.java index 1a6968902e..489caa83a1 100644 --- a/android/app/src/main/java/com/rnuilib/MainApplicationReactNativeHost.java +++ b/android/app/src/main/java/com/rnuilib/MainApplicationReactNativeHost.java @@ -1,101 +1,37 @@ package com.rnuilib; + import android.app.Application; import androidx.annotation.NonNull; -import com.facebook.react.PackageList; -import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; -import com.facebook.react.ReactPackageTurboModuleManagerDelegate; -import com.facebook.react.bridge.JSIModulePackage; -import com.facebook.react.bridge.JSIModuleProvider; -import com.facebook.react.bridge.JSIModuleSpec; -import com.facebook.react.bridge.JSIModuleType; -import com.facebook.react.bridge.JavaScriptContextHolder; -import com.facebook.react.bridge.ReactApplicationContext; -import com.facebook.react.bridge.UIManager; -import com.facebook.react.fabric.ComponentFactory; -import com.facebook.react.fabric.CoreComponentsRegistry; -import com.facebook.react.fabric.EmptyReactNativeConfig; -import com.facebook.react.fabric.FabricJSIModuleProvider; -import com.facebook.react.uimanager.ViewManagerRegistry; +import com.facebook.react.shell.MainReactPackage; import com.rnuilib.BuildConfig; import java.util.ArrayList; import java.util.List; + /** - * A {@link ReactNativeHost} that helps you load everything needed for the New Architecture, both - * TurboModule delegates and the Fabric Renderer. - * - *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the - * `newArchEnabled` property). Is ignored otherwise. + * A simplified {@link ReactNativeHost} for React Native 0.79. */ public class MainApplicationReactNativeHost extends ReactNativeHost { public MainApplicationReactNativeHost(Application application) { super(application); } + @Override public boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } + @Override protected List getPackages() { - List packages = new PackageList(this).getPackages(); - // Packages that cannot be autolinked yet can be added manually here, for example: - // packages.add(new MyReactNativePackage()); - // TurboModules must also be loaded here providing a valid TurboReactPackage implementation: - // packages.add(new TurboReactPackage() { ... }); - // If you have custom Fabric Components, their ViewManagers should also be loaded here - // inside a ReactPackage. + List packages = new ArrayList<>(); + packages.add(new MainReactPackage()); + // Add additional packages here manually as needed return packages; } + @Override protected String getJSMainModuleName() { return "index"; } - @NonNull - @Override - protected ReactPackageTurboModuleManagerDelegate.Builder - getReactPackageTurboModuleManagerDelegateBuilder() { - // Here we provide the ReactPackageTurboModuleManagerDelegate Builder. This is necessary - // for the new architecture and to use TurboModules correctly. - return new MainApplicationTurboModuleManagerDelegate.Builder(); - } - @Override - protected JSIModulePackage getJSIModulePackage() { - return new JSIModulePackage() { - @Override - public List getJSIModules( - final ReactApplicationContext reactApplicationContext, - final JavaScriptContextHolder jsContext) { - final List specs = new ArrayList<>(); - // Here we provide a new JSIModuleSpec that will be responsible of providing the - // custom Fabric Components. - specs.add( - new JSIModuleSpec() { - @Override - public JSIModuleType getJSIModuleType() { - return JSIModuleType.UIManager; - } - @Override - public JSIModuleProvider getJSIModuleProvider() { - final ComponentFactory componentFactory = new ComponentFactory(); - CoreComponentsRegistry.register(componentFactory); - // Here we register a Components Registry. - // The one that is generated with the template contains no components - // and just provides you the one from React Native core. - MainComponentsRegistry.register(componentFactory); - final ReactInstanceManager reactInstanceManager = getReactInstanceManager(); - ViewManagerRegistry viewManagerRegistry = - new ViewManagerRegistry( - reactInstanceManager.getOrCreateViewManagers(reactApplicationContext)); - return new FabricJSIModuleProvider( - reactApplicationContext, - componentFactory, - new EmptyReactNativeConfig(), - viewManagerRegistry); - } - }); - return specs; - } - }; - } } \ No newline at end of file diff --git a/android/app/src/main/java/com/rnuilib/MainComponentsRegistry.java b/android/app/src/main/java/com/rnuilib/MainComponentsRegistry.java index 0361190785..0978e92e92 100644 --- a/android/app/src/main/java/com/rnuilib/MainComponentsRegistry.java +++ b/android/app/src/main/java/com/rnuilib/MainComponentsRegistry.java @@ -1,31 +1,17 @@ package com.rnuilib; -import com.facebook.jni.HybridData; -import com.facebook.proguard.annotations.DoNotStrip; -import com.facebook.react.fabric.ComponentFactory; -import com.facebook.soloader.SoLoader; /** - * Class responsible to load the custom Fabric Components. This class has native methods and needs a - * corresponding C++ implementation/header file to work correctly (already placed inside the jni/ - * folder for you). - * - *

Please note that this class is used ONLY if you opt-in for the New Architecture (see the - * `newArchEnabled` property). Is ignored otherwise. + * Simplified Components Registry for React Native 0.79. + * This is a no-op implementation to avoid compilation issues. */ -@DoNotStrip public class MainComponentsRegistry { - static { - SoLoader.loadLibrary("fabricjni"); + + public static MainComponentsRegistry register(Object componentFactory) { + // No-op implementation for now + return new MainComponentsRegistry(); } - @DoNotStrip private final HybridData mHybridData; - @DoNotStrip - private native HybridData initHybrid(ComponentFactory componentFactory); - @DoNotStrip - private MainComponentsRegistry(ComponentFactory componentFactory) { - mHybridData = initHybrid(componentFactory); + + private MainComponentsRegistry() { + // Empty constructor } - @DoNotStrip - public static MainComponentsRegistry register(ComponentFactory componentFactory) { - return new MainComponentsRegistry(componentFactory); - } -} +} \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index 70e0522b8b..48e97f738e 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -3,11 +3,12 @@ buildscript { ext { buildToolsVersion = "34.0.0" - minSdkVersion = 26 + minSdkVersion = 24 compileSdkVersion = 34 targetSdkVersion = 34 RNNKotlinVersion = "1.8.22" ndkVersion = "25.1.8937393" + AGPVersion = "8.1.1" } repositories { mavenLocal() @@ -15,12 +16,18 @@ buildscript { google() } dependencies { - classpath("com.android.tools.build:gradle") - classpath("com.facebook.react:react-native-gradle-plugin") - classpath("org.jetbrains.kotlin:kotlin-gradle-plugin") - - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$RNNKotlinVersion" + classpath("com.android.tools.build:gradle:$AGPVersion") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$RNNKotlinVersion") + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files } } -apply plugin: "com.facebook.react.rootproject" \ No newline at end of file +allprojects { + repositories { + mavenLocal() + mavenCentral() + google() + maven { url 'https://www.jitpack.io/' } + } +} \ No newline at end of file diff --git a/android/settings.gradle b/android/settings.gradle index b7ef19cd04..59dadd281c 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,6 +1,2 @@ -pluginManagement { includeBuild("../node_modules/@react-native/gradle-plugin") } -plugins { id("com.facebook.react.settings") } -extensions.configure(com.facebook.react.ReactSettingsExtension){ ex -> ex.autolinkLibrariesFromCommand() } rootProject.name = 'rnuilib' include ':app' -includeBuild('../node_modules/@react-native/gradle-plugin') diff --git a/lib/android/build.gradle b/lib/android/build.gradle index 5f0b7d690d..bf83a58908 100644 --- a/lib/android/build.gradle +++ b/lib/android/build.gradle @@ -1,11 +1,11 @@ apply plugin: 'com.android.library' project.ext { - buildToolsVersion = rootProject.ext.has("buildToolsVersion") ? rootProject.ext.buildToolsVersion : '27.0.3' - minSdkVersion = rootProject.ext.has("minSdkVersion") ? rootProject.ext.minSdkVersion : 19 - compileSdkVersion = rootProject.ext.has("compileSdkVersion") ? rootProject.ext.compileSdkVersion : 27 - targetSdkVersion = rootProject.ext.has("targetSdkVersion") ? rootProject.ext.targetSdkVersion : 25 - supportLibVersion = rootProject.ext.has("supportLibVersion") ? rootProject.ext.supportLibVersion : '27.1.1' + buildToolsVersion = rootProject.ext.has("buildToolsVersion") ? rootProject.ext.buildToolsVersion : '34.0.0' + minSdkVersion = rootProject.ext.has("minSdkVersion") ? rootProject.ext.minSdkVersion : 24 + compileSdkVersion = rootProject.ext.has("compileSdkVersion") ? rootProject.ext.compileSdkVersion : 34 + targetSdkVersion = rootProject.ext.has("targetSdkVersion") ? rootProject.ext.targetSdkVersion : 34 + supportLibVersion = rootProject.ext.has("supportLibVersion") ? rootProject.ext.supportLibVersion : '34.0.0' } android { @@ -25,8 +25,23 @@ android { proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } + resourcePrefix "rnuilib_" } +afterEvaluate { + android.libraryVariants.all { variant -> + def variantName = variant.name.capitalize() + + tasks.matching { + it.name == "generate${variantName}RFile" + }.configureEach { + dependsOn "parse${variantName}LocalResources" + } + } +} + + + dependencies { implementation 'com.facebook.react:react-native:+' } diff --git a/lib/android/src/main/java/com/wix/reactnativeuilib/keyboardinput/utils/RuntimeUtils.java b/lib/android/src/main/java/com/wix/reactnativeuilib/keyboardinput/utils/RuntimeUtils.java index 95c5e8ea47..55e0154c36 100644 --- a/lib/android/src/main/java/com/wix/reactnativeuilib/keyboardinput/utils/RuntimeUtils.java +++ b/lib/android/src/main/java/com/wix/reactnativeuilib/keyboardinput/utils/RuntimeUtils.java @@ -6,10 +6,14 @@ public class RuntimeUtils { // TODO Switch to GuardedRunnable when upgrading RN's minimal ver - private static final Runnable sUIUpdateClosure = new Runnable() { - @Override - public void run() { - ReactContextHolder.getContext().getNativeModule(UIManagerModule.class).onBatchComplete(); + private static final Runnable sUIUpdateClosure = () -> { + try{ + UIManagerModule uiManager = ReactContextHolder.getContext().getNativeModule(UIManagerModule.class); + if(uiManager != null){ + uiManager.onBatchComplete(); + } + }catch(Exception e){ + e.printStackTrace(); } }; diff --git a/lib/android/src/main/res/values/R-def.txt b/lib/android/src/main/res/values/R-def.txt new file mode 100644 index 0000000000..7e9943e337 --- /dev/null +++ b/lib/android/src/main/res/values/R-def.txt @@ -0,0 +1,5 @@ + + + RN UI Lib Placeholder + #00000000 + diff --git a/lib/components/DynamicFonts/FontDownloader.d.ts b/lib/components/DynamicFonts/FontDownloader.d.ts new file mode 100644 index 0000000000..677a764b9a --- /dev/null +++ b/lib/components/DynamicFonts/FontDownloader.d.ts @@ -0,0 +1,41 @@ +import { FontExtension } from './FontLoader'; +export type FontDownloaderProps = { + /** + * Should be In the following convention: '/NAME' + */ + dynamicFontsFolder?: string; + downloadErrorMessage?: string; + /** + * Enable debug mode to print extra logs + */ + debug?: boolean; +}; +export default class FontDownloader { + private readonly props; + private readonly fs; + constructor(props: FontDownloaderProps); + private log; + private getPrivateFolder; + private getPrivateFilePath; + private getFileName; + private _isFontDownloaded; + private createPrivateFolderIfNeeded; + private getDownloadFontOptions; + /** + * Download the font + * @param fontUri the remote location of the font + * @param fontName the full name of the font + * @param fontExtension the extension of the font, i.e. '.ttf' or '.otf' + * @param timeout a timeout for the download to fail after + * @returns the full path of the download + */ + downloadFont(fontUri: string, fontName: string, fontExtension: FontExtension, timeout: number): Promise; + /** + * Is the font cached (already downloaded) + * @param {*} fontName the full name of the font + * @param {*} fontExtension the extension of the font, i.e. '.ttf' or '.otf' + */ + isFontDownloaded(fontName: string, fontExtension: FontExtension): Promise; + readFontFromDisk(fontName: string, fontExtension: FontExtension): Promise; + deleteFontFromDisk(fontFullName: string): Promise; +} diff --git a/lib/components/DynamicFonts/FontLoader.d.ts b/lib/components/DynamicFonts/FontLoader.d.ts new file mode 100644 index 0000000000..fd097fec98 --- /dev/null +++ b/lib/components/DynamicFonts/FontLoader.d.ts @@ -0,0 +1,35 @@ +export type FontExtension = 'ttf' | 'otf'; +export type LoadFontInput = { + /** + * Specify registered font name (doesn't work for iOS) + */ + fontName: string; + /** + * This can be a data URI or raw base64... + * if it is raw base64 type must be specified, + * but defaults to TTF (data URI mime: font/ttf or font/otf) + */ + base64FontString: string; + /** + * Specify the type of font in the encoded data (ttf or otf). Defaults to "ttf" + */ + fontExtension: FontExtension; + /** + * Force the loading of the font even if previously loaded it + */ + forceLoad?: boolean; +}; +export type FontLoaderProps = { + /** + * Enable debug mode to print extra logs + */ + debug?: boolean; +}; +export default class FontLoader { + private readonly props; + private readonly loadedFonts; + constructor(props: FontLoaderProps); + private log; + loadFont: ({ fontName, base64FontString, fontExtension, forceLoad }: LoadFontInput) => Promise; + loadFonts: (fonts: LoadFontInput | LoadFontInput[], forceLoad?: boolean) => Promise; +} diff --git a/lib/components/DynamicFonts/NoPermissionsAcquirer.d.ts b/lib/components/DynamicFonts/NoPermissionsAcquirer.d.ts new file mode 100644 index 0000000000..dd90cc521f --- /dev/null +++ b/lib/components/DynamicFonts/NoPermissionsAcquirer.d.ts @@ -0,0 +1,3 @@ +export default class NoPermissionsAcquirer { + getPermissions(): Promise; +} diff --git a/lib/components/DynamicFonts/PermissionsAcquirer.android.d.ts b/lib/components/DynamicFonts/PermissionsAcquirer.android.d.ts new file mode 100644 index 0000000000..5c03958103 --- /dev/null +++ b/lib/components/DynamicFonts/PermissionsAcquirer.android.d.ts @@ -0,0 +1,13 @@ +export type PermissionsAcquirerProps = { + requestPermissionsTitle?: string; + requestPermissionsMessage?: string; + requestPermissionsPositiveButtonText?: string; + permissionsRefusalMessage?: string; + permissionsErrorMessage?: string; +}; +export default class PermissionsAcquirer { + private readonly props; + constructor(props: PermissionsAcquirerProps); + checkPermissions(): Promise; + getPermissions(): Promise; +} diff --git a/lib/components/DynamicFonts/PermissionsAcquirer.ios.d.ts b/lib/components/DynamicFonts/PermissionsAcquirer.ios.d.ts new file mode 100644 index 0000000000..7cd6d112a0 --- /dev/null +++ b/lib/components/DynamicFonts/PermissionsAcquirer.ios.d.ts @@ -0,0 +1,2 @@ +import PermissionsAcquirer from './NoPermissionsAcquirer'; +export default PermissionsAcquirer; diff --git a/lib/components/DynamicFonts/PermissionsAcquirer.web.d.ts b/lib/components/DynamicFonts/PermissionsAcquirer.web.d.ts new file mode 100644 index 0000000000..7cd6d112a0 --- /dev/null +++ b/lib/components/DynamicFonts/PermissionsAcquirer.web.d.ts @@ -0,0 +1,2 @@ +import PermissionsAcquirer from './NoPermissionsAcquirer'; +export default PermissionsAcquirer; diff --git a/lib/components/DynamicFonts/RNFSPackage.d.ts b/lib/components/DynamicFonts/RNFSPackage.d.ts new file mode 100644 index 0000000000..eb386153c6 --- /dev/null +++ b/lib/components/DynamicFonts/RNFSPackage.d.ts @@ -0,0 +1,2 @@ +declare let RNFS: typeof import('react-native-fs') | undefined; +export default RNFS; diff --git a/lib/components/DynamicFonts/index.d.ts b/lib/components/DynamicFonts/index.d.ts new file mode 100644 index 0000000000..a2fa8228e4 --- /dev/null +++ b/lib/components/DynamicFonts/index.d.ts @@ -0,0 +1,61 @@ +import { FontExtension } from './FontLoader'; +import { FontDownloaderProps } from './FontDownloader'; +import type { PermissionsAcquirerProps } from './PermissionsAcquirer.android'; +type DynamicFontsProps = { + fontDownloadingProps?: Omit; + permissionsAcquirerProps?: PermissionsAcquirerProps; + fontLoadErrorMessage?: string; + /** + * Enable debug mode to print extra logs + */ + debug?: boolean; + /** + * Do not request permissions + */ + doNotRequestPermissions?: boolean; +}; +type GetFontInput = { + /** + * The uri of the font (to be downloaded from) + */ + fontUri: string; + /** + * The full name of the font + */ + fontName: string; + /** + * The extension of the font, i.e. '.ttf' or '.otf' + */ + fontExtension: FontExtension; + /** + * Milliseconds for the download to complete in (defaults to 5000) + */ + timeout?: number; +}; +export { DynamicFontsProps, FontExtension, GetFontInput }; +export default class DynamicFonts { + private readonly props; + private readonly permissionsAcquirer; + private readonly fontLoader; + private readonly fontDownloader; + constructor(props: DynamicFontsProps); + private log; + private loadFont; + /** + * Get font - download from uri (or from cache if already downloaded) and load it to memory + * You need to handle errors in the form of Promise.reject + * @param fontUri the uri of the font (to be downloaded from) + * @param fontName the full name of the font + * @param fontExtension the extension of the font, i.e. '.ttf' or '.otf' + * @param timeout milliseconds for the download to complete in (defaults to 5000) + */ + getFont({ fontUri, fontName, fontExtension, timeout }: GetFontInput): Promise; + getFonts(fonts: GetFontInput | GetFontInput[]): Promise; + private buildFontData; + getFontFamily(rootUri: string, fontNames: string[], fontExtension: FontExtension, fontNamePrefix?: string, retries?: number): Promise; + private deleteFontFromDisk; + deleteFont(fontName: string, fontExtension: FontExtension): Promise; + deleteFontFamily(fontNames: string[], fontExtension: FontExtension, fontNamePrefix?: string): Promise; + isFontDownloaded(fontName: string, fontExtension: FontExtension): Promise; + isFontFamilyDownloaded(rootUri: string, fontNames: string[], fontExtension: FontExtension, fontNamePrefix?: string): Promise; +} diff --git a/lib/components/HighlighterOverlayView.d.ts b/lib/components/HighlighterOverlayView.d.ts new file mode 100644 index 0000000000..7608cb0cf9 --- /dev/null +++ b/lib/components/HighlighterOverlayView.d.ts @@ -0,0 +1,34 @@ +import React from 'react'; +import { ViewStyle } from 'react-native'; +type HighlightFrameType = { + x: number; + y: number; + width: number; + height: number; +}; +type HighlightViewTagParams = { + padding: number | ViewStyle['padding']; + offset: Pick; +}; +export type HighlighterOverlayViewProps = { + visible: boolean; + overlayColor?: string; + borderRadius?: number; + strokeColor?: string; + strokeWidth?: number; + onRequestClose?: () => void; + highlightFrame?: HighlightFrameType; + style?: ViewStyle; + highlightViewTag?: number | null; + children?: JSX.Element[] | JSX.Element; + highlightViewTagParams?: HighlightViewTagParams; + minimumRectSize?: Pick; + innerPadding?: number; + accessible?: boolean; + testID?: string; +}; +declare const HighlighterOverlayView: { + (props: HighlighterOverlayViewProps): React.JSX.Element; + displayName: string; +}; +export default HighlighterOverlayView; diff --git a/lib/components/HighlighterOverlayView.web.d.ts b/lib/components/HighlighterOverlayView.web.d.ts new file mode 100644 index 0000000000..7608cb0cf9 --- /dev/null +++ b/lib/components/HighlighterOverlayView.web.d.ts @@ -0,0 +1,34 @@ +import React from 'react'; +import { ViewStyle } from 'react-native'; +type HighlightFrameType = { + x: number; + y: number; + width: number; + height: number; +}; +type HighlightViewTagParams = { + padding: number | ViewStyle['padding']; + offset: Pick; +}; +export type HighlighterOverlayViewProps = { + visible: boolean; + overlayColor?: string; + borderRadius?: number; + strokeColor?: string; + strokeWidth?: number; + onRequestClose?: () => void; + highlightFrame?: HighlightFrameType; + style?: ViewStyle; + highlightViewTag?: number | null; + children?: JSX.Element[] | JSX.Element; + highlightViewTagParams?: HighlightViewTagParams; + minimumRectSize?: Pick; + innerPadding?: number; + accessible?: boolean; + testID?: string; +}; +declare const HighlighterOverlayView: { + (props: HighlighterOverlayViewProps): React.JSX.Element; + displayName: string; +}; +export default HighlighterOverlayView; diff --git a/lib/components/Keyboard/KeyboardInput/CustomKeyboardView/CustomKeyboardView.android.d.ts b/lib/components/Keyboard/KeyboardInput/CustomKeyboardView/CustomKeyboardView.android.d.ts new file mode 100644 index 0000000000..0a053f2a79 --- /dev/null +++ b/lib/components/Keyboard/KeyboardInput/CustomKeyboardView/CustomKeyboardView.android.d.ts @@ -0,0 +1,7 @@ +import React from 'react'; +import CustomKeyboardViewBase, { CustomKeyboardViewBaseProps } from '../CustomKeyboardViewBase'; +export default class CustomKeyboardView extends CustomKeyboardViewBase { + static displayName: string; + componentDidUpdate(prevProps: CustomKeyboardViewBaseProps): Promise; + render(): React.JSX.Element; +} diff --git a/lib/components/Keyboard/KeyboardInput/CustomKeyboardView/CustomKeyboardView.ios.d.ts b/lib/components/Keyboard/KeyboardInput/CustomKeyboardView/CustomKeyboardView.ios.d.ts new file mode 100644 index 0000000000..432d5838c8 --- /dev/null +++ b/lib/components/Keyboard/KeyboardInput/CustomKeyboardView/CustomKeyboardView.ios.d.ts @@ -0,0 +1,19 @@ +import CustomKeyboardViewBase, { CustomKeyboardViewBaseProps } from './../CustomKeyboardViewBase'; +export type CustomKeyboardViewProps = CustomKeyboardViewBaseProps & { + /** + * The reference to the actual text input (or the keyboard may not reset when instructed to, etc.) + */ + inputRef?: any; + useSafeArea?: boolean; +}; +export default class CustomKeyboardView extends CustomKeyboardViewBase { + static displayName: string; + static defaultProps: { + initialProps: {}; + useSafeArea: boolean; + }; + constructor(props: CustomKeyboardViewProps); + componentWillUnmount(): void; + componentDidUpdate(prevProps: CustomKeyboardViewProps): void; + render(): null; +} diff --git a/lib/components/Keyboard/KeyboardInput/CustomKeyboardView/index.d.ts b/lib/components/Keyboard/KeyboardInput/CustomKeyboardView/index.d.ts new file mode 100644 index 0000000000..c10d8d5154 --- /dev/null +++ b/lib/components/Keyboard/KeyboardInput/CustomKeyboardView/index.d.ts @@ -0,0 +1,4 @@ +import React from 'react'; +import { CustomKeyboardViewProps } from './CustomKeyboardView.ios'; +declare const CustomKeyboardView: (props: CustomKeyboardViewProps) => React.JSX.Element; +export default CustomKeyboardView; diff --git a/lib/components/Keyboard/KeyboardInput/CustomKeyboardView/index.web.d.ts b/lib/components/Keyboard/KeyboardInput/CustomKeyboardView/index.web.d.ts new file mode 100644 index 0000000000..e588be6bdf --- /dev/null +++ b/lib/components/Keyboard/KeyboardInput/CustomKeyboardView/index.web.d.ts @@ -0,0 +1,3 @@ +import 'react'; +declare const CustomKeyboardView: () => null; +export default CustomKeyboardView; diff --git a/lib/components/Keyboard/KeyboardInput/CustomKeyboardViewBase.d.ts b/lib/components/Keyboard/KeyboardInput/CustomKeyboardViewBase.d.ts new file mode 100644 index 0000000000..2cc5c2e46d --- /dev/null +++ b/lib/components/Keyboard/KeyboardInput/CustomKeyboardViewBase.d.ts @@ -0,0 +1,24 @@ +import React, { Component } from 'react'; +import { EventSubscription } from 'react-native'; +export type CustomKeyboardViewBaseProps = { + inputRef?: any; + initialProps?: any; + component?: string; + onItemSelected?: (component?: string, args?: any) => void; + onRequestShowKeyboard?: (keyboardId: string) => void; + children?: React.ReactChild | React.ReactChild[]; +}; +export default class CustomKeyboardViewBase extends Component { + static defaultProps: { + initialProps: {}; + }; + registeredRequestShowKeyboard: boolean; + keyboardExpandedToggle: any; + keyboardEventListeners: EventSubscription[]; + constructor(props: T); + shouldComponentUpdate(nextProps: T): boolean; + componentWillUnmount(): void; + addOnItemSelectListener(onItemSelected: CustomKeyboardViewBaseProps['onItemSelected'], component: CustomKeyboardViewBaseProps['component']): void; + componentDidUpdate(prevProps: T): void; + registerListener(props: T, nextProps: T): void; +} diff --git a/lib/components/Keyboard/KeyboardInput/KeyboardAccessoryView.d.ts b/lib/components/Keyboard/KeyboardInput/KeyboardAccessoryView.d.ts new file mode 100644 index 0000000000..b2083493e0 --- /dev/null +++ b/lib/components/Keyboard/KeyboardInput/KeyboardAccessoryView.d.ts @@ -0,0 +1,74 @@ +import React, { Component } from 'react'; +import { LayoutChangeEvent } from 'react-native'; +import { KeyboardTrackingViewProps } from '../KeyboardTracking/KeyboardTrackingView'; +type kbTrackingViewProps = Pick; +export type KeyboardAccessoryViewProps = kbTrackingViewProps & { + /** + * Content to be rendered above the keyboard + */ + renderContent?: () => React.ReactElement; + /** + * iOS only. + * The reference to the actual text input (or the keyboard may not reset when instructed to, etc.). + * This is required. + */ + kbInputRef?: any; + /** + * The keyboard ID (the componentID sent to KeyboardRegistry) + */ + kbComponent?: string; + /** + * The props that will be sent to the KeyboardComponent + */ + kbInitialProps?: any; + /** + * A callback for when the height is changed + */ + onHeightChanged?: (height: number) => void; + /** + * Callback that will be called when an item on the keyboard has been pressed. + */ + onItemSelected?: (component?: string, args?: any) => void; + /** + * Callback that will be called if KeyboardRegistry.requestShowKeyboard is called. + */ + onRequestShowKeyboard?: () => void; + /** + * Callback that will be called once the keyboard has been closed + */ + onKeyboardResigned?: () => void; + children?: React.ReactChild; +}; +/** + * @description: View that allows replacing the default keyboard with other components + * @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/nativeComponentScreens/keyboardAccessory/KeyboardAccessoryViewScreen.js + * @gif: https://github.com/wix/react-native-ui-lib/blob/master/demo/showcase/KeyboardAccessoryView/KeyboardAccessoryView.gif?raw=true + */ +declare class KeyboardAccessoryView extends Component { + static scrollBehaviors: { + NONE: any; + SCROLL_TO_BOTTOM_INVERTED_ONLY: any; + FIXED_OFFSET: any; + }; + static defaultProps: { + revealKeyboardInteractive: boolean; + manageScrollView: boolean; + requiresSameParentToManageScrollView: boolean; + addBottomView: boolean; + allowHitsOutsideBounds: boolean; + scrollBehavior: any; + }; + customInputControllerEventsSubscriber: any; + trackingViewRef: any; + constructor(props: KeyboardAccessoryViewProps); + componentWillUnmount(): void; + onContainerComponentHeightChanged(event: LayoutChangeEvent): void; + onAndroidBackPressed(): boolean; + getNativeProps(): Promise; + registerForKeyboardResignedEvent(): void; + registerAndroidBackHandler(): void; + processInitialProps(): any; + scrollToStart(): void; + render(): React.JSX.Element; +} +export default KeyboardAccessoryView; diff --git a/lib/components/Keyboard/KeyboardInput/KeyboardRegistry.d.ts b/lib/components/Keyboard/KeyboardInput/KeyboardRegistry.d.ts new file mode 100644 index 0000000000..7ac7ab26b7 --- /dev/null +++ b/lib/components/Keyboard/KeyboardInput/KeyboardRegistry.d.ts @@ -0,0 +1,71 @@ +import EventEmitterManager from './utils/EventEmitterManager'; +/** + * @description: used for registering keyboards and performing certain actions on the keyboards. + * @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/nativeComponentScreens/keyboardAccessory/demoKeyboards.js + */ +export default class KeyboardRegistry { + static displayName: string; + static registeredKeyboards: { + [key: string]: any; + }; + static eventEmitter: EventEmitterManager; + /** + * Register a new keyboard. + * componentID (string) - the ID of the keyboard. + * generator (function) - a function for the creation of the keyboard. + * params (object) - to be returned when using other methods (i.e. getKeyboards and getAllKeyboards). + */ + static registerKeyboard: (componentID: string, generator: Function, params?: {}) => void; + /** + * Get a specific keyboard + * componentID (string) - the ID of the keyboard. + */ + static getKeyboard: (componentID: string) => any; + /** + * Get keyboards by IDs + * componentIDs (string[]) - the ID of the keyboard. + */ + static getKeyboards: (componentIDs?: never[]) => any[]; + /** + * Get all keyboards + */ + static getAllKeyboards: () => any[]; + /** + * Add a listener for a callback. + * globalID (string) - ID that includes the componentID and the event name + * (i.e. if componentID='kb1' globalID='kb1.onItemSelected') + * callback (function) - the callback to be called when the said event happens + */ + static addListener: (globalID: string, callback: Function) => void; + /** + * Notify that an event has occurred. + * globalID (string) - ID that includes the componentID and the event name + * (i.e. if componentID='kb1' globalID='kb1.onItemSelected') + * args (object) - data to be sent to the listener. + */ + static notifyListeners: (globalID: string, args: any) => void; + /** + * Remove a listener for a callback. + * globalID (string) - ID that includes the componentID and the event name + * (i.e. if componentID='kb1' globalID='kb1.onItemSelected') + */ + static removeListeners: (globalID: string) => void; + /** + * Default event to be used for when an item on the keyboard has been pressed. + * componentID (string) - the ID of the keyboard. + * args (object) - data to be sent to the listener. + */ + static onItemSelected: (componentID: string, args: any) => void; + /** + * Request to show the keyboard + * componentID (string) - the ID of the keyboard. + */ + static requestShowKeyboard: (componentID: string) => void; + /** + * @deprecated + * iOS only (experimental) + * Call to make the keyboard full screen + * componentID (string) - the ID of the keyboard. + */ + static toggleExpandedKeyboard: (componentID: string) => void; +} diff --git a/lib/components/Keyboard/KeyboardInput/TextInputKeyboardManager/TextInputKeyboardManager.android.d.ts b/lib/components/Keyboard/KeyboardInput/TextInputKeyboardManager/TextInputKeyboardManager.android.d.ts new file mode 100644 index 0000000000..1d98f3b411 --- /dev/null +++ b/lib/components/Keyboard/KeyboardInput/TextInputKeyboardManager/TextInputKeyboardManager.android.d.ts @@ -0,0 +1,4 @@ +export default class TextInputKeyboardManager { + static reset: () => any; + static dismissKeyboard: () => Promise; +} diff --git a/lib/components/Keyboard/KeyboardInput/TextInputKeyboardManager/TextInputKeyboardManager.ios.d.ts b/lib/components/Keyboard/KeyboardInput/TextInputKeyboardManager/TextInputKeyboardManager.ios.d.ts new file mode 100644 index 0000000000..a700b971bf --- /dev/null +++ b/lib/components/Keyboard/KeyboardInput/TextInputKeyboardManager/TextInputKeyboardManager.ios.d.ts @@ -0,0 +1,10 @@ +export default class TextInputKeyboardManager { + static setInputComponent: (textInputRef: any, { component, initialProps, useSafeArea }: { + component?: string | undefined; + initialProps: any; + useSafeArea?: boolean | undefined; + }) => void; + static removeInputComponent: (textInputRef: any) => void; + static dismissKeyboard: () => void; + static toggleExpandKeyboard: (textInputRef: any, expand: boolean, performLayoutAnimation?: boolean) => void; +} diff --git a/lib/components/Keyboard/KeyboardInput/TextInputKeyboardManager/index.d.ts b/lib/components/Keyboard/KeyboardInput/TextInputKeyboardManager/index.d.ts new file mode 100644 index 0000000000..c371b37c94 --- /dev/null +++ b/lib/components/Keyboard/KeyboardInput/TextInputKeyboardManager/index.d.ts @@ -0,0 +1,4 @@ +import { default as TextInputKeyboardManagerIOS } from './TextInputKeyboardManager.ios'; +import { default as TextInputKeyboardManagerAndroid } from './TextInputKeyboardManager.android'; +declare const TextInputKeyboardManager: typeof TextInputKeyboardManagerIOS | typeof TextInputKeyboardManagerAndroid; +export default TextInputKeyboardManager; diff --git a/lib/components/Keyboard/KeyboardInput/utils/EventEmitterManager.d.ts b/lib/components/Keyboard/KeyboardInput/utils/EventEmitterManager.d.ts new file mode 100644 index 0000000000..ccd98a7e98 --- /dev/null +++ b/lib/components/Keyboard/KeyboardInput/utils/EventEmitterManager.d.ts @@ -0,0 +1,10 @@ +export default class EventEmitterManager { + handlerCallbacks: { + [key: string]: Function[]; + }; + constructor(); + listenOn(eventName: string, handlerCallback: Function): void; + emitEvent(eventName: string, params?: {}): void; + removeListeners(eventName: string): void; + removeListener(eventName: string, listener: Function): void; +} diff --git a/lib/components/Keyboard/KeyboardInput/utils/KeyboardUtils.d.ts b/lib/components/Keyboard/KeyboardInput/utils/KeyboardUtils.d.ts new file mode 100644 index 0000000000..c71e5fc53e --- /dev/null +++ b/lib/components/Keyboard/KeyboardInput/utils/KeyboardUtils.d.ts @@ -0,0 +1,11 @@ +/** + * @description: util for managing the keyboard. + * @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/nativeComponentScreens/keyboardInput/KeyboardInputViewScreen.js + */ +export default class KeyboardUtils { + static displayName: string; + /** + * Used to dismiss (close) the keyboard. + */ + static dismiss: () => void; +} diff --git a/lib/components/Keyboard/KeyboardTracking/KeyboardAwareInsetsView.d.ts b/lib/components/Keyboard/KeyboardTracking/KeyboardAwareInsetsView.d.ts new file mode 100644 index 0000000000..4d8992ceaa --- /dev/null +++ b/lib/components/Keyboard/KeyboardTracking/KeyboardAwareInsetsView.d.ts @@ -0,0 +1,16 @@ +import React from 'react'; +import { KeyboardTrackingViewProps } from './KeyboardTrackingView'; +type Props = KeyboardTrackingViewProps & { + offset?: number; +}; +/** + * @description: Used to add an inset when a keyboard is used and might hide part of the screen. + * + * @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/TextFieldScreen/InputsScreen.js + * @notes: This view is useful only for iOS. + */ +declare const KeyboardAwareInsetsView: { + (props: Props): React.JSX.Element; + displayName: string; +}; +export default KeyboardAwareInsetsView; diff --git a/lib/components/Keyboard/KeyboardTracking/KeyboardTrackingView/KeyboardTrackingView.android.d.ts b/lib/components/Keyboard/KeyboardTracking/KeyboardTrackingView/KeyboardTrackingView.android.d.ts new file mode 100644 index 0000000000..933a5f5c42 --- /dev/null +++ b/lib/components/Keyboard/KeyboardTracking/KeyboardTrackingView/KeyboardTrackingView.android.d.ts @@ -0,0 +1,12 @@ +import React, { PureComponent } from 'react'; +declare class KeyboardTrackingView extends PureComponent { + static displayName: string; + render(): React.JSX.Element; + getNativeProps(): Promise<{ + trackingViewHeight: number; + keyboardHeight: number; + contentTopInset: number; + }>; + scrollToStart(): void; +} +export default KeyboardTrackingView; diff --git a/lib/components/Keyboard/KeyboardTracking/KeyboardTrackingView/KeyboardTrackingView.ios.d.ts b/lib/components/Keyboard/KeyboardTracking/KeyboardTrackingView/KeyboardTrackingView.ios.d.ts new file mode 100644 index 0000000000..bd38fdfc4d --- /dev/null +++ b/lib/components/Keyboard/KeyboardTracking/KeyboardTrackingView/KeyboardTrackingView.ios.d.ts @@ -0,0 +1,24 @@ +/** + * Created by artald on 15/05/2016. + */ +import React, { PureComponent } from 'react'; +import { KeyboardTrackingViewProps } from './index'; +/** + * @description: A UI component that enables “keyboard tracking" for this view and it's sub-views. + * Would typically be used when you have a TextField or TextInput inside this view. + * + * @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/nativeComponentScreens/KeyboardTrackingViewScreen.js + * @notes: This view is useful only for iOS. + * @gif: https://github.com/wix/react-native-ui-lib/blob/master/demo/showcase/KeyboardTrackingView/KeyboardTrackingView.gif?raw=true + */ +declare class KeyboardTrackingView extends PureComponent { + static displayName: string; + static defaultProps: { + useSafeArea: boolean; + }; + ref?: any; + render(): React.JSX.Element; + getNativeProps(): Promise; + scrollToStart(): void; +} +export default KeyboardTrackingView; diff --git a/lib/components/Keyboard/KeyboardTracking/KeyboardTrackingView/index.d.ts b/lib/components/Keyboard/KeyboardTracking/KeyboardTrackingView/index.d.ts new file mode 100644 index 0000000000..ed959fee12 --- /dev/null +++ b/lib/components/Keyboard/KeyboardTracking/KeyboardTrackingView/index.d.ts @@ -0,0 +1,81 @@ +import React from 'react'; +import { ViewStyle, ViewProps } from 'react-native'; +declare const SCROLL_BEHAVIORS: { + NONE: any; + SCROLL_TO_BOTTOM_INVERTED_ONLY: any; + FIXED_OFFSET: any; +}; +export type KeyboardTrackingViewProps = ViewProps & { + /** + * Enables tracking of the keyboard when it's dismissed interactively (false by default). + * Why? When using an external keyboard (BT), + * you still get the keyboard events and the view just hovers when you focus the input. + * Also, if you're not using interactive style of dismissing the keyboard + * (or if you don't have an input inside this view) it doesn't make sense to track it anyway. + * (This is caused because of the usage of inputAccessory to be able to track the + * keyboard interactive change and it introduces this bug) + */ + trackInteractive?: boolean; + /** + * iOS only. + * Show the keyboard on a negative scroll + * default: false + */ + revealKeyboardInteractive?: boolean; + /** + * iOS only. + * Set to false to turn off inset management and manage it yourself + * default: true + */ + manageScrollView?: boolean; + /** + * iOS only. + * Set to true manageScrollView is set to true and still does not work, + * it means that the ScrollView found is the wrong one and you'll have + * to have the KeyboardAccessoryView and the ScrollView as siblings + * and set this to true + * + * default: false + */ + requiresSameParentToManageScrollView?: boolean; + /** + * iOS only. + * Allow hitting sub-views that are placed beyond the view bounds + * + * default: false + */ + allowHitsOutsideBounds?: boolean; + scrollToFocusedInput?: boolean; + /** + * iOS only. + * The scrolling behavior (NONE | SCROLL_TO_BOTTOM_INVERTED_ONLY | FIXED_OFFSET) + */ + scrollBehavior?: number; + /** + * iOS only. + * Add a SafeArea view beneath the KeyboardAccessoryView + * default: false + */ + addBottomView?: boolean; + /** + * iOS only. + * The bottom view's color + * default: 'white' + */ + bottomViewColor?: string; + /** + * Allow control safe area + */ + useSafeArea?: boolean; + /** + * Whether or not to include bottom tab bar inset + */ + usesBottomTabs?: boolean; + ref?: any; + style?: ViewStyle; + children?: React.ReactChild | React.ReactChild[]; +}; +declare const _default: React.ForwardRefExoticComponent & React.RefAttributes> & { + scrollBehaviors: typeof SCROLL_BEHAVIORS; +}; +export default _default; diff --git a/lib/components/Keyboard/KeyboardTracking/KeyboardTrackingView/index.web.d.ts b/lib/components/Keyboard/KeyboardTracking/KeyboardTrackingView/index.web.d.ts new file mode 100644 index 0000000000..729fc4a94f --- /dev/null +++ b/lib/components/Keyboard/KeyboardTracking/KeyboardTrackingView/index.web.d.ts @@ -0,0 +1,6 @@ +import 'react'; +declare const KeyboardTrackingView: { + (): null; + scrollBehaviors: {}; +}; +export default KeyboardTrackingView; diff --git a/lib/components/Keyboard/index.d.ts b/lib/components/Keyboard/index.d.ts new file mode 100644 index 0000000000..0c99c823ff --- /dev/null +++ b/lib/components/Keyboard/index.d.ts @@ -0,0 +1,40 @@ +/// +import { KeyboardTrackingViewProps } from './KeyboardTracking/KeyboardTrackingView'; +import KeyboardRegistry from './KeyboardInput/KeyboardRegistry'; +import KeyboardAccessoryView, { KeyboardAccessoryViewProps } from './KeyboardInput/KeyboardAccessoryView'; +import KeyboardUtils from './KeyboardInput/utils/KeyboardUtils'; +export { KeyboardTrackingViewProps, KeyboardAccessoryViewProps }; +declare const _default: { + KeyboardTrackingView: import("react").ForwardRefExoticComponent & import("react").RefAttributes> & { + scrollBehaviors: { + NONE: any; + SCROLL_TO_BOTTOM_INVERTED_ONLY: any; + FIXED_OFFSET: any; + }; + }; + KeyboardAwareInsetsView: { + (props: import("react-native/types").ViewProps & { + trackInteractive?: boolean | undefined; + revealKeyboardInteractive?: boolean | undefined; + manageScrollView?: boolean | undefined; + requiresSameParentToManageScrollView?: boolean | undefined; + allowHitsOutsideBounds?: boolean | undefined; + scrollToFocusedInput?: boolean | undefined; + scrollBehavior?: number | undefined; + addBottomView?: boolean | undefined; + bottomViewColor?: string | undefined; + useSafeArea?: boolean | undefined; + usesBottomTabs?: boolean | undefined; + ref?: any; + style?: import("react-native/types").ViewStyle | undefined; + children?: import("react").ReactChild | import("react").ReactChild[] | undefined; + } & { + offset?: number | undefined; + }): import("react").JSX.Element; + displayName: string; + }; + KeyboardRegistry: typeof KeyboardRegistry; + KeyboardAccessoryView: typeof KeyboardAccessoryView; + KeyboardUtils: typeof KeyboardUtils; +}; +export default _default; diff --git a/lib/components/SafeArea/SafeAreaInsetsManager.d.ts b/lib/components/SafeArea/SafeAreaInsetsManager.d.ts new file mode 100644 index 0000000000..5a4fdb0694 --- /dev/null +++ b/lib/components/SafeArea/SafeAreaInsetsManager.d.ts @@ -0,0 +1,20 @@ +type SafeAreaInsetsType = { + top: number; + left: number; + bottom: number; + right: number; +} | null; +declare class SafeAreaInsetsManager { + _defaultInsets: SafeAreaInsetsType; + _safeAreaInsets: SafeAreaInsetsType; + _safeAreaChangedDelegates: Array; + constructor(); + addSafeAreaChangedListener(): void; + _updateInsets(): Promise; + getSafeAreaInsets(): Promise; + addSafeAreaChangedDelegate(delegate: any): void; + removeSafeAreaChangedDelegate(delegateToRemove: any): void; + get defaultInsets(): SafeAreaInsetsType; +} +declare const _default: SafeAreaInsetsManager; +export default _default; diff --git a/lib/components/SafeArea/SafeAreaSpacerView.d.ts b/lib/components/SafeArea/SafeAreaSpacerView.d.ts new file mode 100644 index 0000000000..52b4a3a5b7 --- /dev/null +++ b/lib/components/SafeArea/SafeAreaSpacerView.d.ts @@ -0,0 +1,10 @@ +import React from 'react'; +import { ViewStyle } from 'react-native'; +export type SafeAreaSpacerViewProps = { + style?: ViewStyle; +}; +declare const SafeAreaSpacerView: { + ({ style }: SafeAreaSpacerViewProps): React.JSX.Element; + displayName: string; +}; +export default SafeAreaSpacerView; diff --git a/lib/components/SafeArea/SafeAreaSpacerView.web.d.ts b/lib/components/SafeArea/SafeAreaSpacerView.web.d.ts new file mode 100644 index 0000000000..52b4a3a5b7 --- /dev/null +++ b/lib/components/SafeArea/SafeAreaSpacerView.web.d.ts @@ -0,0 +1,10 @@ +import React from 'react'; +import { ViewStyle } from 'react-native'; +export type SafeAreaSpacerViewProps = { + style?: ViewStyle; +}; +declare const SafeAreaSpacerView: { + ({ style }: SafeAreaSpacerViewProps): React.JSX.Element; + displayName: string; +}; +export default SafeAreaSpacerView; diff --git a/lib/components/index.d.ts b/lib/components/index.d.ts new file mode 100644 index 0000000000..16eb703609 --- /dev/null +++ b/lib/components/index.d.ts @@ -0,0 +1,6 @@ +import DynamicFonts, { FontExtension } from './DynamicFonts'; +import HighlighterOverlayView from './HighlighterOverlayView'; +import SafeAreaSpacerView from './SafeArea/SafeAreaSpacerView'; +import SafeAreaInsetsManager from './SafeArea/SafeAreaInsetsManager'; +import Keyboard, { KeyboardTrackingViewProps, KeyboardAccessoryViewProps } from './Keyboard'; +export { DynamicFonts, FontExtension, HighlighterOverlayView, SafeAreaSpacerView, SafeAreaInsetsManager, Keyboard, KeyboardTrackingViewProps, KeyboardAccessoryViewProps }; diff --git a/package.json b/package.json index be8d49763f..4e5ed8242a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "react-native-ui-lib", "version": "7.0.0", - "main": "src/index.ts", + "main": "src/index.js", "types": "src/index.d.ts", "author": "Ethan Sharabi ", "homepage": "https://github.com/wix/react-native-ui-lib", @@ -28,6 +28,7 @@ "xcode": "xed ios", "build:dev": "tsc --p tsconfig.dev.json", "build": "node scripts/build/build.js", + "build:helpers": "node scripts/build/buildHelpers.js", "build:local": "./scripts/build/createLocalPackage.sh", "pre-push": "npm run build:dev && npm run test", "docs:deploy": "./scripts/docs/deployDocs.sh", @@ -73,12 +74,13 @@ "@formatjs/intl-numberformat": "^8.0.4", "@formatjs/intl-pluralrules": "^5.0.3", "@react-native-community/blur": "4.4.1", + "@react-native-community/cli": "^20.0.1", "@react-native-community/datetimepicker": "^3.4.6", "@react-native-community/netinfo": "^5.6.2", - "@react-native/babel-preset": "0.73.21", - "@react-native/eslint-config": "0.73.2", - "@react-native/metro-config": "0.73.5", - "@react-native/typescript-config": "0.73.1", + "@react-native/babel-preset": "0.79.0", + "@react-native/eslint-config": "0.79.0", + "@react-native/metro-config": "0.79.0", + "@react-native/typescript-config": "0.79.0", "@shopify/flash-list": "1.7.6", "@testing-library/react-hooks": "^8.0.1", "@testing-library/react-native": "^11.5.1", @@ -87,7 +89,6 @@ "@types/lodash": "^4.0.0", "@types/prop-types": "^15.5.3", "@types/react": "18.3.7", - "@types/react-native": "0.73.0", "@types/react-test-renderer": "^18.3.0", "@types/tinycolor2": "^1.4.2", "@types/url-parse": "^1.4.3", @@ -104,7 +105,6 @@ "eslint-plugin-react-native": "^4.0.0", "jest": "^29.6.3", "light-date": "^1.2.0", - "metro-react-native-babel-preset": "0.73.10", "moment": "^2.24.0", "object-hash": "^3.0.0", "postcss": "^8.4.21", @@ -114,7 +114,7 @@ "react": "18.2.0", "react-autobind": "^1.0.6", "react-dom": "^18.2.0", - "react-native": "0.73.9", + "react-native": "0.79.0", "react-native-fs": "^2.20.0", "react-native-gesture-handler": "2.14.1", "react-native-haptic-feedback": "^1.11.0", @@ -128,11 +128,11 @@ "react-test-renderer": "18.2.0", "reassure": "^0.4.1", "shell-utils": "^1.0.10", - "typescript": "5.0.4" + "typescript": "^5.0.4" }, "peerDependencies": { "react": ">=17.0.1", - "react-native": ">=0.64.1", + "react-native": ">=0.79.0", "react-native-gesture-handler": ">=2.5.0", "react-native-reanimated": ">=2.0.0", "react-native-ui-lib": "*" @@ -164,7 +164,7 @@ }, "packageManager": "yarn@3.4.1", "files": [ - "*.js", + "*.js","*.ts", "*.d.ts", "!scripts", "scripts/release/prReleaseNotesCommon.js", @@ -172,7 +172,7 @@ "src", "testkit", "ReactNativeUILib.podspec", - "!src/**/*.ts", + "src/**/*.ts", "src/**/*.d.ts", "!src/**/*.tsx", "!src/**/__tests__", diff --git a/scripts/build/buildHelpers.js b/scripts/build/buildHelpers.js new file mode 100755 index 0000000000..3256ea36b1 --- /dev/null +++ b/scripts/build/buildHelpers.js @@ -0,0 +1,64 @@ +#!/usr/bin/env node + +const childProcess = require('child_process'); +const fs = require('fs'); +const path = require('path'); + +console.info('## Building react-native-ui-lib for Metro bundler ##'); + +const BABEL_OPTIONS = `--config-file ./src/.babelrc.json --extensions '.ts,.tsx' --ignore "src/**/*.d.ts"`; + +const directories = [ + 'src/helpers', + 'src/commons', + 'src/style', + 'src/services', + 'src/incubator', + 'src/hooks', + 'src/assets', + 'src/components' +]; + +try { + // Build all source directories + for (const dir of directories) { + console.info(`## Transpiling ${dir} ##`); + childProcess.execSync(`./node_modules/.bin/babel ${dir} --out-dir ${dir} ${BABEL_OPTIONS}`, {stdio: 'inherit'}); + } + + // Build main index file + console.info('## Transpiling main index file ##'); + childProcess.execSync(`./node_modules/.bin/babel src/index.ts --out-file src/index.js ${BABEL_OPTIONS}`, {stdio: 'inherit'}); + + // Fix import extensions for Metro compatibility + console.info('## Fixing import extensions for Metro bundler ##'); + const helpersIndexPath = path.join(__dirname, '../../src/helpers/index.js'); + let helpersIndexContent = fs.readFileSync(helpersIndexPath, 'utf8'); + + // Add .js extensions to relative imports for Metro bundler compatibility + helpersIndexContent = helpersIndexContent + .replace('from "./AvatarHelper"', 'from "./AvatarHelper.js"') + .replace('from "./Profiler"', 'from "./Profiler.js"'); + + fs.writeFileSync(helpersIndexPath, helpersIndexContent, 'utf8'); + + // Update package.json to point to JS file + console.info('## Updating package.json main entry ##'); + const packagePath = path.join(__dirname, '../../package.json'); + const package = JSON.parse(fs.readFileSync(packagePath, 'utf8')); + + if (package.main !== 'src/index.js') { + package.main = 'src/index.js'; + fs.writeFileSync(packagePath, JSON.stringify(package, null, 2), {encoding: 'utf8'}); + console.info('## Updated package.json main entry to src/index.js ##'); + } else { + console.info('## package.json main entry already correct ##'); + } + + console.info('## ✅ Complete build successful! ##'); + console.info('## The library should now work correctly with Metro bundler ##'); +} catch (error) { + console.error('## ❌ Build failed ##'); + console.error(error.message); + process.exit(1); +} diff --git a/src/assets/Assets.d.ts b/src/assets/Assets.d.ts new file mode 100644 index 0000000000..7d530b09a2 --- /dev/null +++ b/src/assets/Assets.d.ts @@ -0,0 +1,5 @@ +export declare class Assets { + [key: string]: any; + loadAssetsGroup(groupName: string, assets: object): this; + getAssetByPath(path: string): this[string]; +} diff --git a/src/assets/Assets.js b/src/assets/Assets.js new file mode 100644 index 0000000000..09415b11ea --- /dev/null +++ b/src/assets/Assets.js @@ -0,0 +1,49 @@ +import _get from "lodash/get"; +import _isPlainObject from "lodash/isPlainObject"; +import _isString from "lodash/isString"; +function assignProperties(a, b) { + if (a) { + Object.keys(b).forEach(key => { + // @ts-ignore + Object.defineProperty(a, key, Object.getOwnPropertyDescriptor(b, key)); + }); + } + return a; +} +function ensurePath(obj, path) { + let pointer = obj; + const pathArray = path.split('.'); + const n = pathArray.length; + for (let i = 0; i < n; i++) { + const segment = pathArray[i]; + if (pointer[segment]) { + const descriptor = Object.getOwnPropertyDescriptor(pointer, segment); + if (descriptor?.get) { + Object.defineProperty(pointer, segment, descriptor); + } + } else { + pointer[segment] = pointer[segment] || {}; + } + pointer = pointer[segment]; + } + return pointer; +} +export class Assets { + loadAssetsGroup(groupName, assets) { + if (!_isString(groupName)) { + throw new Error('group name should be a string'); + } + if (!_isPlainObject(assets)) { + throw new Error('assets should be a hash map or a function (for lazy access)'); + } + if (groupName === '') { + assignProperties(this, assets); + } else { + assignProperties(ensurePath(this, groupName), assets); + } + return this; + } + getAssetByPath(path) { + return _get(this, path); + } +} \ No newline at end of file diff --git a/src/assets/emojis/index.d.ts b/src/assets/emojis/index.d.ts new file mode 100644 index 0000000000..24501bccf9 --- /dev/null +++ b/src/assets/emojis/index.d.ts @@ -0,0 +1,1343 @@ +export declare const emojis: { + readonly "100": "💯"; + readonly "1234": "🔢"; + readonly interrobang: "⁉️"; + readonly tm: "™️"; + readonly information_source: "ℹ️"; + readonly left_right_arrow: "↔️"; + readonly arrow_up_down: "↕️"; + readonly arrow_upper_left: "↖️"; + readonly arrow_upper_right: "↗️"; + readonly arrow_lower_right: "↘️"; + readonly arrow_lower_left: "↙️"; + readonly keyboard: "⌨"; + readonly sunny: "☀️"; + readonly cloud: "☁️"; + readonly umbrella: "☔️"; + readonly showman: "☃"; + readonly comet: "☄"; + readonly ballot_box_with_check: "☑️"; + readonly coffee: "☕️"; + readonly shamrock: "☘"; + readonly skull_and_crossbones: "☠"; + readonly radioactive_sign: "☢"; + readonly biohazard_sign: "☣"; + readonly orthodox_cross: "☦"; + readonly wheel_of_dharma: "☸"; + readonly white_frowning_face: "☹"; + readonly aries: "♈️"; + readonly taurus: "♉️"; + readonly sagittarius: "♐️"; + readonly capricorn: "♑️"; + readonly aquarius: "♒️"; + readonly pisces: "♓️"; + readonly spades: "♠️"; + readonly clubs: "♣️"; + readonly hearts: "♥️"; + readonly diamonds: "♦️"; + readonly hotsprings: "♨️"; + readonly hammer_and_pick: "⚒"; + readonly anchor: "⚓️"; + readonly crossed_swords: "⚔"; + readonly scales: "⚖"; + readonly alembic: "⚗"; + readonly gear: "⚙"; + readonly scissors: "✂️"; + readonly white_check_mark: "✅"; + readonly airplane: "✈️"; + readonly email: "✉️"; + readonly envelope: "✉️"; + readonly black_nib: "✒️"; + readonly heavy_check_mark: "✔️"; + readonly heavy_multiplication_x: "✖️"; + readonly star_of_david: "✡"; + readonly sparkles: "✨"; + readonly eight_spoked_asterisk: "✳️"; + readonly eight_pointed_black_star: "✴️"; + readonly snowflake: "❄️"; + readonly sparkle: "❇️"; + readonly question: "❓"; + readonly grey_question: "❔"; + readonly grey_exclamation: "❕"; + readonly exclamation: "❗️"; + readonly heavy_exclamation_mark: "❗️"; + readonly heavy_heart_exclamation_mark_ornament: "❣"; + readonly heart: "❤️"; + readonly heavy_plus_sign: "➕"; + readonly heavy_minus_sign: "➖"; + readonly heavy_division_sign: "➗"; + readonly arrow_heading_up: "⤴️"; + readonly arrow_heading_down: "⤵️"; + readonly wavy_dash: "〰️"; + readonly congratulations: "㊗️"; + readonly secret: "㊙️"; + readonly copyright: "©️"; + readonly registered: "®️"; + readonly bangbang: "‼️"; + readonly leftwards_arrow_with_hook: "↩️"; + readonly arrow_right_hook: "↪️"; + readonly watch: "⌚️"; + readonly hourglass: "⌛️"; + readonly fast_forward: "⏩"; + readonly rewind: "⏪"; + readonly arrow_double_up: "⏫"; + readonly arrow_double_down: "⏬"; + readonly black_right_pointing_double_triangle_with_vertical_bar: "⏭"; + readonly black_left_pointing_double_triangle_with_vertical_bar: "⏮"; + readonly black_right_pointing_triangle_with_double_vertical_bar: "⏯"; + readonly alarm_clock: "⏰"; + readonly stopwatch: "⏱"; + readonly timer_clock: "⏲"; + readonly hourglass_flowing_sand: "⏳"; + readonly double_vertical_bar: "⏸"; + readonly black_square_for_stop: "⏹"; + readonly black_circle_for_record: "⏺"; + readonly m: "Ⓜ️"; + readonly black_small_square: "▪️"; + readonly white_small_square: "▫️"; + readonly arrow_forward: "▶️"; + readonly arrow_backward: "◀️"; + readonly white_medium_square: "◻️"; + readonly black_medium_square: "◼️"; + readonly white_medium_small_square: "◽️"; + readonly black_medium_small_square: "◾️"; + readonly phone: "☎️"; + readonly telephone: "☎️"; + readonly point_up: "☝️"; + readonly star_and_crescent: "☪"; + readonly peace_symbol: "☮"; + readonly yin_yang: "☯"; + readonly relaxed: "☺️"; + readonly gemini: "♊️"; + readonly cancer: "♋️"; + readonly leo: "♌️"; + readonly virgo: "♍️"; + readonly libra: "♎️"; + readonly scorpius: "♏️"; + readonly recycle: "♻️"; + readonly wheelchair: "♿️"; + readonly atom_symbol: "⚛"; + readonly fleur_de_lis: "⚜"; + readonly warning: "⚠️"; + readonly zap: "⚡️"; + readonly white_circle: "⚪️"; + readonly black_circle: "⚫️"; + readonly coffin: "⚰"; + readonly funeral_urn: "⚱"; + readonly soccer: "⚽️"; + readonly baseball: "⚾️"; + readonly snowman: "⛄️"; + readonly partly_sunny: "⛅️"; + readonly thunder_cloud_and_rain: "⛈"; + readonly ophiuchus: "⛎"; + readonly pick: "⛏"; + readonly helmet_with_white_cross: "⛑"; + readonly chains: "⛓"; + readonly no_entry: "⛔️"; + readonly shinto_shrine: "⛩"; + readonly church: "⛪️"; + readonly mountain: "⛰"; + readonly umbrella_on_ground: "⛱"; + readonly fountain: "⛲️"; + readonly golf: "⛳️"; + readonly ferry: "⛴"; + readonly boat: "⛵️"; + readonly sailboat: "⛵️"; + readonly skier: "⛷"; + readonly ice_skate: "⛸"; + readonly person_with_ball: "⛹"; + readonly tent: "⛺️"; + readonly fuelpump: "⛽️"; + readonly fist: "✊"; + readonly hand: "✋"; + readonly raised_hand: "✋"; + readonly v: "✌️"; + readonly writing_hand: "✍"; + readonly pencil2: "✏️"; + readonly latin_cross: "✝"; + readonly x: "❌"; + readonly negative_squared_cross_mark: "❎"; + readonly arrow_right: "➡️"; + readonly curly_loop: "➰"; + readonly loop: "➿"; + readonly arrow_left: "⬅️"; + readonly arrow_up: "⬆️"; + readonly arrow_down: "⬇️"; + readonly black_large_square: "⬛️"; + readonly white_large_square: "⬜️"; + readonly star: "⭐️"; + readonly o: "⭕️"; + readonly part_alternation_mark: "〽️"; + readonly mahjong: "🀄️"; + readonly black_joker: "🃏"; + readonly a: "🅰️"; + readonly b: "🅱️"; + readonly o2: "🅾️"; + readonly parking: "🅿️"; + readonly ab: "🆎"; + readonly cl: "🆑"; + readonly cool: "🆒"; + readonly free: "🆓"; + readonly id: "🆔"; + readonly new: "🆕"; + readonly ng: "🆖"; + readonly ok: "🆗"; + readonly sos: "🆘"; + readonly up: "🆙"; + readonly vs: "🆚"; + readonly koko: "🈁"; + readonly sa: "🈂️"; + readonly u7121: "🈚️"; + readonly u6307: "🈯️"; + readonly u7981: "🈲"; + readonly u7a7a: "🈳"; + readonly u5408: "🈴"; + readonly u6e80: "🈵"; + readonly u6709: "🈶"; + readonly u6708: "🈷️"; + readonly u7533: "🈸"; + readonly u5272: "🈹"; + readonly u55b6: "🈺"; + readonly ideograph_advantage: "🉐"; + readonly accept: "🉑"; + readonly cyclone: "🌀"; + readonly foggy: "🌁"; + readonly closed_umbrella: "🌂"; + readonly night_with_stars: "🌃"; + readonly sunrise_over_mountains: "🌄"; + readonly sunrise: "🌅"; + readonly city_sunset: "🌆"; + readonly city_sunrise: "🌇"; + readonly rainbow: "🌈"; + readonly bridge_at_night: "🌉"; + readonly ocean: "🌊"; + readonly volcano: "🌋"; + readonly milky_way: "🌌"; + readonly earth_africa: "🌍"; + readonly earth_americas: "🌎"; + readonly earth_asia: "🌏"; + readonly globe_with_meridians: "🌐"; + readonly new_moon: "🌑"; + readonly waxing_crescent_moon: "🌒"; + readonly first_quarter_moon: "🌓"; + readonly moon: "🌔"; + readonly waxing_gibbous_moon: "🌔"; + readonly full_moon: "🌕"; + readonly waning_gibbous_moon: "🌖"; + readonly last_quarter_moon: "🌗"; + readonly waning_crescent_moon: "🌘"; + readonly crescent_moon: "🌙"; + readonly new_moon_with_face: "🌚"; + readonly first_quarter_moon_with_face: "🌛"; + readonly last_quarter_moon_with_face: "🌜"; + readonly full_moon_with_face: "🌝"; + readonly sun_with_face: "🌞"; + readonly star2: "🌟"; + readonly stars: "🌠"; + readonly thermometer: "🌡"; + readonly mostly_sunny: "🌤"; + readonly sun_small_cloud: "🌤"; + readonly barely_sunny: "🌥"; + readonly sun_behind_cloud: "🌥"; + readonly partly_sunny_rain: "🌦"; + readonly sun_behind_rain_cloud: "🌦"; + readonly rain_cloud: "🌧"; + readonly snow_cloud: "🌨"; + readonly lightning: "🌩"; + readonly lightning_cloud: "🌩"; + readonly tornado: "🌪"; + readonly tornado_cloud: "🌪"; + readonly fog: "🌫"; + readonly wind_blowing_face: "🌬"; + readonly hotdog: "🌭"; + readonly taco: "🌮"; + readonly burrito: "🌯"; + readonly chestnut: "🌰"; + readonly seedling: "🌱"; + readonly evergreen_tree: "🌲"; + readonly deciduous_tree: "🌳"; + readonly palm_tree: "🌴"; + readonly cactus: "🌵"; + readonly hot_pepper: "🌶"; + readonly tulip: "🌷"; + readonly cherry_blossom: "🌸"; + readonly rose: "🌹"; + readonly hibiscus: "🌺"; + readonly sunflower: "🌻"; + readonly blossom: "🌼"; + readonly corn: "🌽"; + readonly ear_of_rice: "🌾"; + readonly herb: "🌿"; + readonly four_leaf_clover: "🍀"; + readonly maple_leaf: "🍁"; + readonly fallen_leaf: "🍂"; + readonly leaves: "🍃"; + readonly mushroom: "🍄"; + readonly tomato: "🍅"; + readonly eggplant: "🍆"; + readonly grapes: "🍇"; + readonly melon: "🍈"; + readonly watermelon: "🍉"; + readonly tangerine: "🍊"; + readonly lemon: "🍋"; + readonly banana: "🍌"; + readonly pineapple: "🍍"; + readonly apple: "🍎"; + readonly green_apple: "🍏"; + readonly pear: "🍐"; + readonly peach: "🍑"; + readonly cherries: "🍒"; + readonly strawberry: "🍓"; + readonly hamburger: "🍔"; + readonly pizza: "🍕"; + readonly meat_on_bone: "🍖"; + readonly poultry_leg: "🍗"; + readonly rice_cracker: "🍘"; + readonly rice_ball: "🍙"; + readonly rice: "🍚"; + readonly curry: "🍛"; + readonly ramen: "🍜"; + readonly spaghetti: "🍝"; + readonly bread: "🍞"; + readonly fries: "🍟"; + readonly sweet_potato: "🍠"; + readonly dango: "🍡"; + readonly oden: "🍢"; + readonly sushi: "🍣"; + readonly fried_shrimp: "🍤"; + readonly fish_cake: "🍥"; + readonly icecream: "🍦"; + readonly shaved_ice: "🍧"; + readonly ice_cream: "🍨"; + readonly doughnut: "🍩"; + readonly cookie: "🍪"; + readonly chocolate_bar: "🍫"; + readonly candy: "🍬"; + readonly lollipop: "🍭"; + readonly custard: "🍮"; + readonly honey_pot: "🍯"; + readonly cake: "🍰"; + readonly bento: "🍱"; + readonly stew: "🍲"; + readonly egg: "🍳"; + readonly fork_and_knife: "🍴"; + readonly tea: "🍵"; + readonly sake: "🍶"; + readonly wine_glass: "🍷"; + readonly cocktail: "🍸"; + readonly tropical_drink: "🍹"; + readonly beer: "🍺"; + readonly beers: "🍻"; + readonly baby_bottle: "🍼"; + readonly knife_fork_plate: "🍽"; + readonly champagne: "🍾"; + readonly popcorn: "🍿"; + readonly ribbon: "🎀"; + readonly gift: "🎁"; + readonly birthday: "🎂"; + readonly jack_o_lantern: "🎃"; + readonly christmas_tree: "🎄"; + readonly santa: "🎅"; + readonly fireworks: "🎆"; + readonly sparkler: "🎇"; + readonly balloon: "🎈"; + readonly tada: "🎉"; + readonly confetti_ball: "🎊"; + readonly tanabata_tree: "🎋"; + readonly crossed_flags: "🎌"; + readonly bamboo: "🎍"; + readonly dolls: "🎎"; + readonly flags: "🎏"; + readonly wind_chime: "🎐"; + readonly rice_scene: "🎑"; + readonly school_satchel: "🎒"; + readonly mortar_board: "🎓"; + readonly medal: "🎖"; + readonly reminder_ribbon: "🎗"; + readonly studio_microphone: "🎙"; + readonly level_slider: "🎚"; + readonly control_knobs: "🎛"; + readonly film_frames: "🎞"; + readonly admission_tickets: "🎟"; + readonly carousel_horse: "🎠"; + readonly ferris_wheel: "🎡"; + readonly roller_coaster: "🎢"; + readonly fishing_pole_and_fish: "🎣"; + readonly microphone: "🎤"; + readonly movie_camera: "🎥"; + readonly cinema: "🎦"; + readonly headphones: "🎧"; + readonly art: "🎨"; + readonly tophat: "🎩"; + readonly circus_tent: "🎪"; + readonly ticket: "🎫"; + readonly clapper: "🎬"; + readonly performing_arts: "🎭"; + readonly video_game: "🎮"; + readonly dart: "🎯"; + readonly slot_machine: "🎰"; + readonly "8ball": "🎱"; + readonly game_die: "🎲"; + readonly bowling: "🎳"; + readonly flower_playing_cards: "🎴"; + readonly musical_note: "🎵"; + readonly notes: "🎶"; + readonly saxophone: "🎷"; + readonly guitar: "🎸"; + readonly musical_keyboard: "🎹"; + readonly trumpet: "🎺"; + readonly violin: "🎻"; + readonly musical_score: "🎼"; + readonly running_shirt_with_sash: "🎽"; + readonly tennis: "🎾"; + readonly ski: "🎿"; + readonly basketball: "🏀"; + readonly checkered_flag: "🏁"; + readonly snowboarder: "🏂"; + readonly runner: "🏃"; + readonly running: "🏃"; + readonly surfer: "🏄"; + readonly sports_medal: "🏅"; + readonly trophy: "🏆"; + readonly horse_racing: "🏇"; + readonly football: "🏈"; + readonly rugby_football: "🏉"; + readonly swimmer: "🏊"; + readonly weight_lifter: "🏋"; + readonly golfer: "🏌"; + readonly racing_motorcycle: "🏍"; + readonly racing_car: "🏎"; + readonly cricket_bat_and_ball: "🏏"; + readonly volleyball: "🏐"; + readonly field_hockey_stick_and_ball: "🏑"; + readonly ice_hockey_stick_and_puck: "🏒"; + readonly table_tennis_paddle_and_ball: "🏓"; + readonly snow_capped_mountain: "🏔"; + readonly camping: "🏕"; + readonly beach_with_umbrella: "🏖"; + readonly building_construction: "🏗"; + readonly house_buildings: "🏘"; + readonly cityscape: "🏙"; + readonly derelict_house_building: "🏚"; + readonly classical_building: "🏛"; + readonly desert: "🏜"; + readonly desert_island: "🏝"; + readonly national_park: "🏞"; + readonly stadium: "🏟"; + readonly house: "🏠"; + readonly house_with_garden: "🏡"; + readonly office: "🏢"; + readonly post_office: "🏣"; + readonly european_post_office: "🏤"; + readonly hospital: "🏥"; + readonly bank: "🏦"; + readonly atm: "🏧"; + readonly hotel: "🏨"; + readonly love_hotel: "🏩"; + readonly convenience_store: "🏪"; + readonly school: "🏫"; + readonly department_store: "🏬"; + readonly factory: "🏭"; + readonly izakaya_lantern: "🏮"; + readonly lantern: "🏮"; + readonly japanese_castle: "🏯"; + readonly european_castle: "🏰"; + readonly waving_white_flag: "🏳"; + readonly waving_black_flag: "🏴"; + readonly rosette: "🏵"; + readonly label: "🏷"; + readonly badminton_racquet_and_shuttlecock: "🏸"; + readonly bow_and_arrow: "🏹"; + readonly amphora: "🏺"; + readonly "skin-tone-2": "🏻"; + readonly "skin-tone-3": "🏼"; + readonly "skin-tone-4": "🏽"; + readonly "skin-tone-5": "🏾"; + readonly "skin-tone-6": "🏿"; + readonly rat: "🐀"; + readonly mouse2: "🐁"; + readonly ox: "🐂"; + readonly water_buffalo: "🐃"; + readonly cow2: "🐄"; + readonly tiger2: "🐅"; + readonly leopard: "🐆"; + readonly rabbit2: "🐇"; + readonly cat2: "🐈"; + readonly dragon: "🐉"; + readonly crocodile: "🐊"; + readonly whale2: "🐋"; + readonly snail: "🐌"; + readonly snake: "🐍"; + readonly racehorse: "🐎"; + readonly ram: "🐏"; + readonly goat: "🐐"; + readonly sheep: "🐑"; + readonly monkey: "🐒"; + readonly rooster: "🐓"; + readonly chicken: "🐔"; + readonly dog2: "🐕"; + readonly pig2: "🐖"; + readonly boar: "🐗"; + readonly elephant: "🐘"; + readonly octopus: "🐙"; + readonly shell: "🐚"; + readonly bug: "🐛"; + readonly ant: "🐜"; + readonly bee: "🐝"; + readonly honeybee: "🐝"; + readonly beetle: "🐞"; + readonly fish: "🐟"; + readonly tropical_fish: "🐠"; + readonly blowfish: "🐡"; + readonly turtle: "🐢"; + readonly hatching_chick: "🐣"; + readonly baby_chick: "🐤"; + readonly hatched_chick: "🐥"; + readonly bird: "🐦"; + readonly penguin: "🐧"; + readonly koala: "🐨"; + readonly poodle: "🐩"; + readonly dromedary_camel: "🐪"; + readonly camel: "🐫"; + readonly dolphin: "🐬"; + readonly flipper: "🐬"; + readonly mouse: "🐭"; + readonly cow: "🐮"; + readonly tiger: "🐯"; + readonly rabbit: "🐰"; + readonly cat: "🐱"; + readonly dragon_face: "🐲"; + readonly whale: "🐳"; + readonly horse: "🐴"; + readonly monkey_face: "🐵"; + readonly dog: "🐶"; + readonly pig: "🐷"; + readonly frog: "🐸"; + readonly hamster: "🐹"; + readonly wolf: "🐺"; + readonly bear: "🐻"; + readonly panda_face: "🐼"; + readonly pig_nose: "🐽"; + readonly feet: "🐾"; + readonly paw_prints: "🐾"; + readonly chipmunk: "🐿"; + readonly eyes: "👀"; + readonly eye: "👁"; + readonly ear: "👂"; + readonly nose: "👃"; + readonly lips: "👄"; + readonly tongue: "👅"; + readonly point_up_2: "👆"; + readonly point_down: "👇"; + readonly point_left: "👈"; + readonly point_right: "👉"; + readonly facepunch: "👊"; + readonly punch: "👊"; + readonly wave: "👋"; + readonly ok_hand: "👌"; + readonly "+1": "👍"; + readonly thumbsup: "👍"; + readonly "-1": "👎"; + readonly thumbsdown: "👎"; + readonly clap: "👏"; + readonly open_hands: "👐"; + readonly crown: "👑"; + readonly womans_hat: "👒"; + readonly eyeglasses: "👓"; + readonly necktie: "👔"; + readonly shirt: "👕"; + readonly tshirt: "👕"; + readonly jeans: "👖"; + readonly dress: "👗"; + readonly kimono: "👘"; + readonly bikini: "👙"; + readonly womans_clothes: "👚"; + readonly purse: "👛"; + readonly handbag: "👜"; + readonly pouch: "👝"; + readonly mans_shoe: "👞"; + readonly shoe: "👞"; + readonly athletic_shoe: "👟"; + readonly high_heel: "👠"; + readonly sandal: "👡"; + readonly boot: "👢"; + readonly footprints: "👣"; + readonly bust_in_silhouette: "👤"; + readonly busts_in_silhouette: "👥"; + readonly boy: "👦"; + readonly girl: "👧"; + readonly man: "👨"; + readonly woman: "👩"; + readonly family: "👨‍👩‍👦"; + readonly "man-woman-boy": "👨‍👩‍👦"; + readonly couple: "👫"; + readonly man_and_woman_holding_hands: "👫"; + readonly two_men_holding_hands: "👬"; + readonly two_women_holding_hands: "👭"; + readonly cop: "👮"; + readonly dancers: "👯"; + readonly bride_with_veil: "👰"; + readonly person_with_blond_hair: "👱"; + readonly man_with_gua_pi_mao: "👲"; + readonly man_with_turban: "👳"; + readonly older_man: "👴"; + readonly older_woman: "👵"; + readonly baby: "👶"; + readonly construction_worker: "👷"; + readonly princess: "👸"; + readonly japanese_ogre: "👹"; + readonly japanese_goblin: "👺"; + readonly ghost: "👻"; + readonly angel: "👼"; + readonly alien: "👽"; + readonly space_invader: "👾"; + readonly imp: "👿"; + readonly skull: "💀"; + readonly information_desk_person: "💁"; + readonly guardsman: "💂"; + readonly dancer: "💃"; + readonly lipstick: "💄"; + readonly nail_care: "💅"; + readonly massage: "💆"; + readonly haircut: "💇"; + readonly barber: "💈"; + readonly syringe: "💉"; + readonly pill: "💊"; + readonly kiss: "💋"; + readonly love_letter: "💌"; + readonly ring: "💍"; + readonly gem: "💎"; + readonly couplekiss: "💏"; + readonly bouquet: "💐"; + readonly couple_with_heart: "💑"; + readonly wedding: "💒"; + readonly heartbeat: "💓"; + readonly broken_heart: "💔"; + readonly two_hearts: "💕"; + readonly sparkling_heart: "💖"; + readonly heartpulse: "💗"; + readonly cupid: "💘"; + readonly blue_heart: "💙"; + readonly green_heart: "💚"; + readonly yellow_heart: "💛"; + readonly purple_heart: "💜"; + readonly gift_heart: "💝"; + readonly revolving_hearts: "💞"; + readonly heart_decoration: "💟"; + readonly diamond_shape_with_a_dot_inside: "💠"; + readonly bulb: "💡"; + readonly anger: "💢"; + readonly bomb: "💣"; + readonly zzz: "💤"; + readonly boom: "💥"; + readonly collision: "💥"; + readonly sweat_drops: "💦"; + readonly droplet: "💧"; + readonly dash: "💨"; + readonly hankey: "💩"; + readonly poop: "💩"; + readonly shit: "💩"; + readonly muscle: "💪"; + readonly dizzy: "💫"; + readonly speech_balloon: "💬"; + readonly thought_balloon: "💭"; + readonly white_flower: "💮"; + readonly moneybag: "💰"; + readonly currency_exchange: "💱"; + readonly heavy_dollar_sign: "💲"; + readonly credit_card: "💳"; + readonly yen: "💴"; + readonly dollar: "💵"; + readonly euro: "💶"; + readonly pound: "💷"; + readonly money_with_wings: "💸"; + readonly chart: "💹"; + readonly seat: "💺"; + readonly computer: "💻"; + readonly briefcase: "💼"; + readonly minidisc: "💽"; + readonly floppy_disk: "💾"; + readonly cd: "💿"; + readonly dvd: "📀"; + readonly file_folder: "📁"; + readonly open_file_folder: "📂"; + readonly page_with_curl: "📃"; + readonly page_facing_up: "📄"; + readonly date: "📅"; + readonly calendar: "📆"; + readonly card_index: "📇"; + readonly chart_with_upwards_trend: "📈"; + readonly chart_with_downwards_trend: "📉"; + readonly bar_chart: "📊"; + readonly clipboard: "📋"; + readonly pushpin: "📌"; + readonly round_pushpin: "📍"; + readonly paperclip: "📎"; + readonly straight_ruler: "📏"; + readonly triangular_ruler: "📐"; + readonly bookmark_tabs: "📑"; + readonly ledger: "📒"; + readonly notebook: "📓"; + readonly notebook_with_decorative_cover: "📔"; + readonly closed_book: "📕"; + readonly book: "📖"; + readonly open_book: "📖"; + readonly green_book: "📗"; + readonly blue_book: "📘"; + readonly orange_book: "📙"; + readonly books: "📚"; + readonly name_badge: "📛"; + readonly scroll: "📜"; + readonly memo: "📝"; + readonly pencil: "📝"; + readonly telephone_receiver: "📞"; + readonly pager: "📟"; + readonly fax: "📠"; + readonly satellite: "🛰"; + readonly loudspeaker: "📢"; + readonly mega: "📣"; + readonly outbox_tray: "📤"; + readonly inbox_tray: "📥"; + readonly package: "📦"; + readonly "e-mail": "📧"; + readonly incoming_envelope: "📨"; + readonly envelope_with_arrow: "📩"; + readonly mailbox_closed: "📪"; + readonly mailbox: "📫"; + readonly mailbox_with_mail: "📬"; + readonly mailbox_with_no_mail: "📭"; + readonly postbox: "📮"; + readonly postal_horn: "📯"; + readonly newspaper: "📰"; + readonly iphone: "📱"; + readonly calling: "📲"; + readonly vibration_mode: "📳"; + readonly mobile_phone_off: "📴"; + readonly no_mobile_phones: "📵"; + readonly signal_strength: "📶"; + readonly camera: "📷"; + readonly camera_with_flash: "📸"; + readonly video_camera: "📹"; + readonly tv: "📺"; + readonly radio: "📻"; + readonly vhs: "📼"; + readonly film_projector: "📽"; + readonly prayer_beads: "📿"; + readonly twisted_rightwards_arrows: "🔀"; + readonly repeat: "🔁"; + readonly repeat_one: "🔂"; + readonly arrows_clockwise: "🔃"; + readonly arrows_counterclockwise: "🔄"; + readonly low_brightness: "🔅"; + readonly high_brightness: "🔆"; + readonly mute: "🔇"; + readonly speaker: "🔈"; + readonly sound: "🔉"; + readonly loud_sound: "🔊"; + readonly battery: "🔋"; + readonly electric_plug: "🔌"; + readonly mag: "🔍"; + readonly mag_right: "🔎"; + readonly lock_with_ink_pen: "🔏"; + readonly closed_lock_with_key: "🔐"; + readonly key: "🔑"; + readonly lock: "🔒"; + readonly unlock: "🔓"; + readonly bell: "🔔"; + readonly no_bell: "🔕"; + readonly bookmark: "🔖"; + readonly link: "🔗"; + readonly radio_button: "🔘"; + readonly back: "🔙"; + readonly end: "🔚"; + readonly on: "🔛"; + readonly soon: "🔜"; + readonly top: "🔝"; + readonly underage: "🔞"; + readonly keycap_ten: "🔟"; + readonly capital_abcd: "🔠"; + readonly abcd: "🔡"; + readonly symbols: "🔣"; + readonly abc: "🔤"; + readonly fire: "🔥"; + readonly flashlight: "🔦"; + readonly wrench: "🔧"; + readonly hammer: "🔨"; + readonly nut_and_bolt: "🔩"; + readonly hocho: "🔪"; + readonly knife: "🔪"; + readonly gun: "🔫"; + readonly microscope: "🔬"; + readonly telescope: "🔭"; + readonly crystal_ball: "🔮"; + readonly six_pointed_star: "🔯"; + readonly beginner: "🔰"; + readonly trident: "🔱"; + readonly black_square_button: "🔲"; + readonly white_square_button: "🔳"; + readonly red_circle: "🔴"; + readonly large_blue_circle: "🔵"; + readonly large_orange_diamond: "🔶"; + readonly large_blue_diamond: "🔷"; + readonly small_orange_diamond: "🔸"; + readonly small_blue_diamond: "🔹"; + readonly small_red_triangle: "🔺"; + readonly small_red_triangle_down: "🔻"; + readonly arrow_up_small: "🔼"; + readonly arrow_down_small: "🔽"; + readonly om_symbol: "🕉"; + readonly dove_of_peace: "🕊"; + readonly kaaba: "🕋"; + readonly mosque: "🕌"; + readonly synagogue: "🕍"; + readonly menorah_with_nine_branches: "🕎"; + readonly clock1: "🕐"; + readonly clock2: "🕑"; + readonly clock3: "🕒"; + readonly clock4: "🕓"; + readonly clock5: "🕔"; + readonly clock6: "🕕"; + readonly clock7: "🕖"; + readonly clock8: "🕗"; + readonly clock9: "🕘"; + readonly clock10: "🕙"; + readonly clock11: "🕚"; + readonly clock12: "🕛"; + readonly clock130: "🕜"; + readonly clock230: "🕝"; + readonly clock330: "🕞"; + readonly clock430: "🕟"; + readonly clock530: "🕠"; + readonly clock630: "🕡"; + readonly clock730: "🕢"; + readonly clock830: "🕣"; + readonly clock930: "🕤"; + readonly clock1030: "🕥"; + readonly clock1130: "🕦"; + readonly clock1230: "🕧"; + readonly candle: "🕯"; + readonly mantelpiece_clock: "🕰"; + readonly hole: "🕳"; + readonly man_in_business_suit_levitating: "🕴"; + readonly sleuth_or_spy: "🕵"; + readonly dark_sunglasses: "🕶"; + readonly spider: "🕷"; + readonly spider_web: "🕸"; + readonly joystick: "🕹"; + readonly linked_paperclips: "🖇"; + readonly lower_left_ballpoint_pen: "🖊"; + readonly lower_left_fountain_pen: "🖋"; + readonly lower_left_paintbrush: "🖌"; + readonly lower_left_crayon: "🖍"; + readonly raised_hand_with_fingers_splayed: "🖐"; + readonly middle_finger: "🖕"; + readonly reversed_hand_with_middle_finger_extended: "🖕"; + readonly "spock-hand": "🖖"; + readonly desktop_computer: "🖥"; + readonly printer: "🖨"; + readonly three_button_mouse: "🖱"; + readonly trackball: "🖲"; + readonly frame_with_picture: "🖼"; + readonly card_index_dividers: "🗂"; + readonly card_file_box: "🗃"; + readonly file_cabinet: "🗄"; + readonly wastebasket: "🗑"; + readonly spiral_note_pad: "🗒"; + readonly spiral_calendar_pad: "🗓"; + readonly compression: "🗜"; + readonly old_key: "🗝"; + readonly rolled_up_newspaper: "🗞"; + readonly dagger_knife: "🗡"; + readonly speaking_head_in_silhouette: "🗣"; + readonly left_speech_bubble: "🗨"; + readonly right_anger_bubble: "🗯"; + readonly ballot_box_with_ballot: "🗳"; + readonly world_map: "🗺"; + readonly mount_fuji: "🗻"; + readonly tokyo_tower: "🗼"; + readonly statue_of_liberty: "🗽"; + readonly japan: "🗾"; + readonly moyai: "🗿"; + readonly grinning: "😀"; + readonly grin: "😁"; + readonly joy: "😂"; + readonly smiley: "😃"; + readonly smile: "😄"; + readonly sweat_smile: "😅"; + readonly laughing: "😆"; + readonly satisfied: "😆"; + readonly innocent: "😇"; + readonly smiling_imp: "😈"; + readonly wink: "😉"; + readonly blush: "😊"; + readonly yum: "😋"; + readonly relieved: "😌"; + readonly heart_eyes: "😍"; + readonly sunglasses: "😎"; + readonly smirk: "😏"; + readonly neutral_face: "😐"; + readonly expressionless: "😑"; + readonly unamused: "😒"; + readonly sweat: "😓"; + readonly pensive: "😔"; + readonly confused: "😕"; + readonly confounded: "😖"; + readonly kissing: "😗"; + readonly kissing_heart: "😘"; + readonly kissing_smiling_eyes: "😙"; + readonly kissing_closed_eyes: "😚"; + readonly stuck_out_tongue: "😛"; + readonly stuck_out_tongue_winking_eye: "😜"; + readonly stuck_out_tongue_closed_eyes: "😝"; + readonly disappointed: "😞"; + readonly worried: "😟"; + readonly angry: "😠"; + readonly rage: "😡"; + readonly cry: "😢"; + readonly persevere: "😣"; + readonly triumph: "😤"; + readonly disappointed_relieved: "😥"; + readonly frowning: "😦"; + readonly anguished: "😧"; + readonly fearful: "😨"; + readonly weary: "😩"; + readonly sleepy: "😪"; + readonly tired_face: "😫"; + readonly grimacing: "😬"; + readonly sob: "😭"; + readonly open_mouth: "😮"; + readonly hushed: "😯"; + readonly cold_sweat: "😰"; + readonly scream: "😱"; + readonly astonished: "😲"; + readonly flushed: "😳"; + readonly sleeping: "😴"; + readonly dizzy_face: "😵"; + readonly no_mouth: "😶"; + readonly mask: "😷"; + readonly smile_cat: "😸"; + readonly joy_cat: "😹"; + readonly smiley_cat: "😺"; + readonly heart_eyes_cat: "😻"; + readonly smirk_cat: "😼"; + readonly kissing_cat: "😽"; + readonly pouting_cat: "😾"; + readonly crying_cat_face: "😿"; + readonly scream_cat: "🙀"; + readonly slightly_frowning_face: "🙁"; + readonly slightly_smiling_face: "🙂"; + readonly upside_down_face: "🙃"; + readonly face_with_rolling_eyes: "🙄"; + readonly no_good: "🙅"; + readonly ok_woman: "🙆"; + readonly bow: "🙇"; + readonly see_no_evil: "🙈"; + readonly hear_no_evil: "🙉"; + readonly speak_no_evil: "🙊"; + readonly raising_hand: "🙋"; + readonly raised_hands: "🙌"; + readonly person_frowning: "🙍"; + readonly person_with_pouting_face: "🙎"; + readonly pray: "🙏"; + readonly rocket: "🚀"; + readonly helicopter: "🚁"; + readonly steam_locomotive: "🚂"; + readonly railway_car: "🚃"; + readonly bullettrain_side: "🚄"; + readonly bullettrain_front: "🚅"; + readonly train2: "🚆"; + readonly metro: "🚇"; + readonly light_rail: "🚈"; + readonly station: "🚉"; + readonly tram: "🚊"; + readonly train: "🚋"; + readonly bus: "🚌"; + readonly oncoming_bus: "🚍"; + readonly trolleybus: "🚎"; + readonly busstop: "🚏"; + readonly minibus: "🚐"; + readonly ambulance: "🚑"; + readonly fire_engine: "🚒"; + readonly police_car: "🚓"; + readonly oncoming_police_car: "🚔"; + readonly taxi: "🚕"; + readonly oncoming_taxi: "🚖"; + readonly car: "🚗"; + readonly red_car: "🚗"; + readonly oncoming_automobile: "🚘"; + readonly blue_car: "🚙"; + readonly truck: "🚚"; + readonly articulated_lorry: "🚛"; + readonly tractor: "🚜"; + readonly monorail: "🚝"; + readonly mountain_railway: "🚞"; + readonly suspension_railway: "🚟"; + readonly mountain_cableway: "🚠"; + readonly aerial_tramway: "🚡"; + readonly ship: "🚢"; + readonly rowboat: "🚣"; + readonly speedboat: "🚤"; + readonly traffic_light: "🚥"; + readonly vertical_traffic_light: "🚦"; + readonly construction: "🚧"; + readonly rotating_light: "🚨"; + readonly triangular_flag_on_post: "🚩"; + readonly door: "🚪"; + readonly no_entry_sign: "🚫"; + readonly smoking: "🚬"; + readonly no_smoking: "🚭"; + readonly put_litter_in_its_place: "🚮"; + readonly do_not_litter: "🚯"; + readonly potable_water: "🚰"; + readonly "non-potable_water": "🚱"; + readonly bike: "🚲"; + readonly no_bicycles: "🚳"; + readonly bicyclist: "🚴"; + readonly mountain_bicyclist: "🚵"; + readonly walking: "🚶"; + readonly no_pedestrians: "🚷"; + readonly children_crossing: "🚸"; + readonly mens: "🚹"; + readonly womens: "🚺"; + readonly restroom: "🚻"; + readonly baby_symbol: "🚼"; + readonly toilet: "🚽"; + readonly wc: "🚾"; + readonly shower: "🚿"; + readonly bath: "🛀"; + readonly bathtub: "🛁"; + readonly passport_control: "🛂"; + readonly customs: "🛃"; + readonly baggage_claim: "🛄"; + readonly left_luggage: "🛅"; + readonly couch_and_lamp: "🛋"; + readonly sleeping_accommodation: "🛌"; + readonly shopping_bags: "🛍"; + readonly bellhop_bell: "🛎"; + readonly bed: "🛏"; + readonly place_of_worship: "🛐"; + readonly hammer_and_wrench: "🛠"; + readonly shield: "🛡"; + readonly oil_drum: "🛢"; + readonly motorway: "🛣"; + readonly railway_track: "🛤"; + readonly motor_boat: "🛥"; + readonly small_airplane: "🛩"; + readonly airplane_departure: "🛫"; + readonly airplane_arriving: "🛬"; + readonly passenger_ship: "🛳"; + readonly zipper_mouth_face: "🤐"; + readonly money_mouth_face: "🤑"; + readonly face_with_thermometer: "🤒"; + readonly nerd_face: "🤓"; + readonly thinking_face: "🤔"; + readonly face_with_head_bandage: "🤕"; + readonly robot_face: "🤖"; + readonly hugging_face: "🤗"; + readonly the_horns: "🤘"; + readonly sign_of_the_horns: "🤘"; + readonly crab: "🦀"; + readonly lion_face: "🦁"; + readonly scorpion: "🦂"; + readonly turkey: "🦃"; + readonly unicorn_face: "🦄"; + readonly cheese_wedge: "🧀"; + readonly hash: "#️⃣"; + readonly keycap_star: "*⃣"; + readonly zero: "0️⃣"; + readonly one: "1️⃣"; + readonly two: "2️⃣"; + readonly three: "3️⃣"; + readonly four: "4️⃣"; + readonly five: "5️⃣"; + readonly six: "6️⃣"; + readonly seven: "7️⃣"; + readonly eight: "8️⃣"; + readonly nine: "9️⃣"; + readonly "flag-ac": "🇦🇨"; + readonly "flag-ad": "🇦🇩"; + readonly "flag-ae": "🇦🇪"; + readonly "flag-af": "🇦🇫"; + readonly "flag-ag": "🇦🇬"; + readonly "flag-ai": "🇦🇮"; + readonly "flag-al": "🇦🇱"; + readonly "flag-am": "🇦🇲"; + readonly "flag-ao": "🇦🇴"; + readonly "flag-aq": "🇦🇶"; + readonly "flag-ar": "🇦🇷"; + readonly "flag-as": "🇦🇸"; + readonly "flag-at": "🇦🇹"; + readonly "flag-au": "🇦🇺"; + readonly "flag-aw": "🇦🇼"; + readonly "flag-ax": "🇦🇽"; + readonly "flag-az": "🇦🇿"; + readonly "flag-ba": "🇧🇦"; + readonly "flag-bb": "🇧🇧"; + readonly "flag-bd": "🇧🇩"; + readonly "flag-be": "🇧🇪"; + readonly "flag-bf": "🇧🇫"; + readonly "flag-bg": "🇧🇬"; + readonly "flag-bh": "🇧🇭"; + readonly "flag-bi": "🇧🇮"; + readonly "flag-bj": "🇧🇯"; + readonly "flag-bl": "🇧🇱"; + readonly "flag-bm": "🇧🇲"; + readonly "flag-bn": "🇧🇳"; + readonly "flag-bo": "🇧🇴"; + readonly "flag-bq": "🇧🇶"; + readonly "flag-br": "🇧🇷"; + readonly "flag-bs": "🇧🇸"; + readonly "flag-bt": "🇧🇹"; + readonly "flag-bv": "🇧🇻"; + readonly "flag-bw": "🇧🇼"; + readonly "flag-by": "🇧🇾"; + readonly "flag-bz": "🇧🇿"; + readonly "flag-ca": "🇨🇦"; + readonly "flag-cc": "🇨🇨"; + readonly "flag-cd": "🇨🇩"; + readonly "flag-cf": "🇨🇫"; + readonly "flag-cg": "🇨🇬"; + readonly "flag-ch": "🇨🇭"; + readonly "flag-ci": "🇨🇮"; + readonly "flag-ck": "🇨🇰"; + readonly "flag-cl": "🇨🇱"; + readonly "flag-cm": "🇨🇲"; + readonly "flag-cn": "🇨🇳"; + readonly cn: "🇨🇳"; + readonly "flag-co": "🇨🇴"; + readonly "flag-cp": "🇨🇵"; + readonly "flag-cr": "🇨🇷"; + readonly "flag-cu": "🇨🇺"; + readonly "flag-cv": "🇨🇻"; + readonly "flag-cw": "🇨🇼"; + readonly "flag-cx": "🇨🇽"; + readonly "flag-cy": "🇨🇾"; + readonly "flag-cz": "🇨🇿"; + readonly "flag-de": "🇩🇪"; + readonly de: "🇩🇪"; + readonly "flag-dg": "🇩🇬"; + readonly "flag-dj": "🇩🇯"; + readonly "flag-dk": "🇩🇰"; + readonly "flag-dm": "🇩🇲"; + readonly "flag-do": "🇩🇴"; + readonly "flag-dz": "🇩🇿"; + readonly "flag-ea": "🇪🇦"; + readonly "flag-ec": "🇪🇨"; + readonly "flag-ee": "🇪🇪"; + readonly "flag-eg": "🇪🇬"; + readonly "flag-eh": "🇪🇭"; + readonly "flag-er": "🇪🇷"; + readonly "flag-es": "🇪🇸"; + readonly es: "🇪🇸"; + readonly "flag-et": "🇪🇹"; + readonly "flag-eu": "🇪🇺"; + readonly "flag-fi": "🇫🇮"; + readonly "flag-fj": "🇫🇯"; + readonly "flag-fk": "🇫🇰"; + readonly "flag-fm": "🇫🇲"; + readonly "flag-fo": "🇫🇴"; + readonly "flag-fr": "🇫🇷"; + readonly fr: "🇫🇷"; + readonly "flag-ga": "🇬🇦"; + readonly "flag-gb": "🇬🇧"; + readonly gb: "🇬🇧"; + readonly uk: "🇬🇧"; + readonly "flag-gd": "🇬🇩"; + readonly "flag-ge": "🇬🇪"; + readonly "flag-gf": "🇬🇫"; + readonly "flag-gg": "🇬🇬"; + readonly "flag-gh": "🇬🇭"; + readonly "flag-gi": "🇬🇮"; + readonly "flag-gl": "🇬🇱"; + readonly "flag-gm": "🇬🇲"; + readonly "flag-gn": "🇬🇳"; + readonly "flag-gp": "🇬🇵"; + readonly "flag-gq": "🇬🇶"; + readonly "flag-gr": "🇬🇷"; + readonly "flag-gs": "🇬🇸"; + readonly "flag-gt": "🇬🇹"; + readonly "flag-gu": "🇬🇺"; + readonly "flag-gw": "🇬🇼"; + readonly "flag-gy": "🇬🇾"; + readonly "flag-hk": "🇭🇰"; + readonly "flag-hm": "🇭🇲"; + readonly "flag-hn": "🇭🇳"; + readonly "flag-hr": "🇭🇷"; + readonly "flag-ht": "🇭🇹"; + readonly "flag-hu": "🇭🇺"; + readonly "flag-ic": "🇮🇨"; + readonly "flag-id": "🇮🇩"; + readonly "flag-ie": "🇮🇪"; + readonly "flag-il": "🇮🇱"; + readonly "flag-im": "🇮🇲"; + readonly "flag-in": "🇮🇳"; + readonly "flag-io": "🇮🇴"; + readonly "flag-iq": "🇮🇶"; + readonly "flag-ir": "🇮🇷"; + readonly "flag-is": "🇮🇸"; + readonly "flag-it": "🇮🇹"; + readonly it: "🇮🇹"; + readonly "flag-je": "🇯🇪"; + readonly "flag-jm": "🇯🇲"; + readonly "flag-jo": "🇯🇴"; + readonly "flag-jp": "🇯🇵"; + readonly jp: "🇯🇵"; + readonly "flag-ke": "🇰🇪"; + readonly "flag-kg": "🇰🇬"; + readonly "flag-kh": "🇰🇭"; + readonly "flag-ki": "🇰🇮"; + readonly "flag-km": "🇰🇲"; + readonly "flag-kn": "🇰🇳"; + readonly "flag-kp": "🇰🇵"; + readonly "flag-kr": "🇰🇷"; + readonly kr: "🇰🇷"; + readonly "flag-kw": "🇰🇼"; + readonly "flag-ky": "🇰🇾"; + readonly "flag-kz": "🇰🇿"; + readonly "flag-la": "🇱🇦"; + readonly "flag-lb": "🇱🇧"; + readonly "flag-lc": "🇱🇨"; + readonly "flag-li": "🇱🇮"; + readonly "flag-lk": "🇱🇰"; + readonly "flag-lr": "🇱🇷"; + readonly "flag-ls": "🇱🇸"; + readonly "flag-lt": "🇱🇹"; + readonly "flag-lu": "🇱🇺"; + readonly "flag-lv": "🇱🇻"; + readonly "flag-ly": "🇱🇾"; + readonly "flag-ma": "🇲🇦"; + readonly "flag-mc": "🇲🇨"; + readonly "flag-md": "🇲🇩"; + readonly "flag-me": "🇲🇪"; + readonly "flag-mf": "🇲🇫"; + readonly "flag-mg": "🇲🇬"; + readonly "flag-mh": "🇲🇭"; + readonly "flag-mk": "🇲🇰"; + readonly "flag-ml": "🇲🇱"; + readonly "flag-mm": "🇲🇲"; + readonly "flag-mn": "🇲🇳"; + readonly "flag-mo": "🇲🇴"; + readonly "flag-mp": "🇲🇵"; + readonly "flag-mq": "🇲🇶"; + readonly "flag-mr": "🇲🇷"; + readonly "flag-ms": "🇲🇸"; + readonly "flag-mt": "🇲🇹"; + readonly "flag-mu": "🇲🇺"; + readonly "flag-mv": "🇲🇻"; + readonly "flag-mw": "🇲🇼"; + readonly "flag-mx": "🇲🇽"; + readonly "flag-my": "🇲🇾"; + readonly "flag-mz": "🇲🇿"; + readonly "flag-na": "🇳🇦"; + readonly "flag-nc": "🇳🇨"; + readonly "flag-ne": "🇳🇪"; + readonly "flag-nf": "🇳🇫"; + readonly "flag-ng": "🇳🇬"; + readonly "flag-ni": "🇳🇮"; + readonly "flag-nl": "🇳🇱"; + readonly "flag-no": "🇳🇴"; + readonly "flag-np": "🇳🇵"; + readonly "flag-nr": "🇳🇷"; + readonly "flag-nu": "🇳🇺"; + readonly "flag-nz": "🇳🇿"; + readonly "flag-om": "🇴🇲"; + readonly "flag-pa": "🇵🇦"; + readonly "flag-pe": "🇵🇪"; + readonly "flag-pf": "🇵🇫"; + readonly "flag-pg": "🇵🇬"; + readonly "flag-ph": "🇵🇭"; + readonly "flag-pk": "🇵🇰"; + readonly "flag-pl": "🇵🇱"; + readonly "flag-pm": "🇵🇲"; + readonly "flag-pn": "🇵🇳"; + readonly "flag-pr": "🇵🇷"; + readonly "flag-ps": "🇵🇸"; + readonly "flag-pt": "🇵🇹"; + readonly "flag-pw": "🇵🇼"; + readonly "flag-py": "🇵🇾"; + readonly "flag-qa": "🇶🇦"; + readonly "flag-re": "🇷🇪"; + readonly "flag-ro": "🇷🇴"; + readonly "flag-rs": "🇷🇸"; + readonly "flag-ru": "🇷🇺"; + readonly ru: "🇷🇺"; + readonly "flag-rw": "🇷🇼"; + readonly "flag-sa": "🇸🇦"; + readonly "flag-sb": "🇸🇧"; + readonly "flag-sc": "🇸🇨"; + readonly "flag-sd": "🇸🇩"; + readonly "flag-se": "🇸🇪"; + readonly "flag-sg": "🇸🇬"; + readonly "flag-sh": "🇸🇭"; + readonly "flag-si": "🇸🇮"; + readonly "flag-sj": "🇸🇯"; + readonly "flag-sk": "🇸🇰"; + readonly "flag-sl": "🇸🇱"; + readonly "flag-sm": "🇸🇲"; + readonly "flag-sn": "🇸🇳"; + readonly "flag-so": "🇸🇴"; + readonly "flag-sr": "🇸🇷"; + readonly "flag-ss": "🇸🇸"; + readonly "flag-st": "🇸🇹"; + readonly "flag-sv": "🇸🇻"; + readonly "flag-sx": "🇸🇽"; + readonly "flag-sy": "🇸🇾"; + readonly "flag-sz": "🇸🇿"; + readonly "flag-ta": "🇹🇦"; + readonly "flag-tc": "🇹🇨"; + readonly "flag-td": "🇹🇩"; + readonly "flag-tf": "🇹🇫"; + readonly "flag-tg": "🇹🇬"; + readonly "flag-th": "🇹🇭"; + readonly "flag-tj": "🇹🇯"; + readonly "flag-tk": "🇹🇰"; + readonly "flag-tl": "🇹🇱"; + readonly "flag-tm": "🇹🇲"; + readonly "flag-tn": "🇹🇳"; + readonly "flag-to": "🇹🇴"; + readonly "flag-tr": "🇹🇷"; + readonly "flag-tt": "🇹🇹"; + readonly "flag-tv": "🇹🇻"; + readonly "flag-tw": "🇹🇼"; + readonly "flag-tz": "🇹🇿"; + readonly "flag-ua": "🇺🇦"; + readonly "flag-ug": "🇺🇬"; + readonly "flag-um": "🇺🇲"; + readonly "flag-us": "🇺🇸"; + readonly us: "🇺🇸"; + readonly "flag-uy": "🇺🇾"; + readonly "flag-uz": "🇺🇿"; + readonly "flag-va": "🇻🇦"; + readonly "flag-vc": "🇻🇨"; + readonly "flag-ve": "🇻🇪"; + readonly "flag-vg": "🇻🇬"; + readonly "flag-vi": "🇻🇮"; + readonly "flag-vn": "🇻🇳"; + readonly "flag-vu": "🇻🇺"; + readonly "flag-wf": "🇼🇫"; + readonly "flag-ws": "🇼🇸"; + readonly "flag-xk": "🇽🇰"; + readonly "flag-ye": "🇾🇪"; + readonly "flag-yt": "🇾🇹"; + readonly "flag-za": "🇿🇦"; + readonly "flag-zm": "🇿🇲"; + readonly "flag-zw": "🇿🇼"; + readonly "man-man-boy": "👨‍👨‍👦"; + readonly "man-man-boy-boy": "👨‍👨‍👦‍👦"; + readonly "man-man-girl": "👨‍👨‍👧"; + readonly "man-man-girl-boy": "👨‍👨‍👧‍👦"; + readonly "man-man-girl-girl": "👨‍👨‍👧‍👧"; + readonly "man-woman-boy-boy": "👨‍👩‍👦‍👦"; + readonly "man-woman-girl": "👨‍👩‍👧"; + readonly "man-woman-girl-boy": "👨‍👩‍👧‍👦"; + readonly "man-woman-girl-girl": "👨‍👩‍👧‍👧"; + readonly "man-heart-man": "👨‍❤️‍👨"; + readonly "man-kiss-man": "👨‍❤️‍💋‍👨"; + readonly "woman-woman-boy": "👩‍👩‍👦"; + readonly "woman-woman-boy-boy": "👩‍👩‍👦‍👦"; + readonly "woman-woman-girl": "👩‍👩‍👧"; + readonly "woman-woman-girl-boy": "👩‍👩‍👧‍👦"; + readonly "woman-woman-girl-girl": "👩‍👩‍👧‍👧"; + readonly "woman-heart-woman": "👩‍❤️‍👩"; + readonly "woman-kiss-woman": "👩‍❤️‍💋‍👩"; +}; diff --git a/src/assets/emojis/index.js b/src/assets/emojis/index.js new file mode 100644 index 0000000000..cc6f26b4ab --- /dev/null +++ b/src/assets/emojis/index.js @@ -0,0 +1,1345 @@ +/*eslint-disable*/ + +export const emojis = { + "100": "💯", + "1234": "🔢", + "interrobang": "⁉️", + "tm": "™️", + "information_source": "ℹ️", + "left_right_arrow": "↔️", + "arrow_up_down": "↕️", + "arrow_upper_left": "↖️", + "arrow_upper_right": "↗️", + "arrow_lower_right": "↘️", + "arrow_lower_left": "↙️", + "keyboard": "⌨", + "sunny": "☀️", + "cloud": "☁️", + "umbrella": "☔️", + "showman": "☃", + "comet": "☄", + "ballot_box_with_check": "☑️", + "coffee": "☕️", + "shamrock": "☘", + "skull_and_crossbones": "☠", + "radioactive_sign": "☢", + "biohazard_sign": "☣", + "orthodox_cross": "☦", + "wheel_of_dharma": "☸", + "white_frowning_face": "☹", + "aries": "♈️", + "taurus": "♉️", + "sagittarius": "♐️", + "capricorn": "♑️", + "aquarius": "♒️", + "pisces": "♓️", + "spades": "♠️", + "clubs": "♣️", + "hearts": "♥️", + "diamonds": "♦️", + "hotsprings": "♨️", + "hammer_and_pick": "⚒", + "anchor": "⚓️", + "crossed_swords": "⚔", + "scales": "⚖", + "alembic": "⚗", + "gear": "⚙", + "scissors": "✂️", + "white_check_mark": "✅", + "airplane": "✈️", + "email": "✉️", + "envelope": "✉️", + "black_nib": "✒️", + "heavy_check_mark": "✔️", + "heavy_multiplication_x": "✖️", + "star_of_david": "✡", + "sparkles": "✨", + "eight_spoked_asterisk": "✳️", + "eight_pointed_black_star": "✴️", + "snowflake": "❄️", + "sparkle": "❇️", + "question": "❓", + "grey_question": "❔", + "grey_exclamation": "❕", + "exclamation": "❗️", + "heavy_exclamation_mark": "❗️", + "heavy_heart_exclamation_mark_ornament": "❣", + "heart": "❤️", + "heavy_plus_sign": "➕", + "heavy_minus_sign": "➖", + "heavy_division_sign": "➗", + "arrow_heading_up": "⤴️", + "arrow_heading_down": "⤵️", + "wavy_dash": "〰️", + "congratulations": "㊗️", + "secret": "㊙️", + "copyright": "©️", + "registered": "®️", + "bangbang": "‼️", + "leftwards_arrow_with_hook": "↩️", + "arrow_right_hook": "↪️", + "watch": "⌚️", + "hourglass": "⌛️", + "fast_forward": "⏩", + "rewind": "⏪", + "arrow_double_up": "⏫", + "arrow_double_down": "⏬", + "black_right_pointing_double_triangle_with_vertical_bar": "⏭", + "black_left_pointing_double_triangle_with_vertical_bar": "⏮", + "black_right_pointing_triangle_with_double_vertical_bar": "⏯", + "alarm_clock": "⏰", + "stopwatch": "⏱", + "timer_clock": "⏲", + "hourglass_flowing_sand": "⏳", + "double_vertical_bar": "⏸", + "black_square_for_stop": "⏹", + "black_circle_for_record": "⏺", + "m": "Ⓜ️", + "black_small_square": "▪️", + "white_small_square": "▫️", + "arrow_forward": "▶️", + "arrow_backward": "◀️", + "white_medium_square": "◻️", + "black_medium_square": "◼️", + "white_medium_small_square": "◽️", + "black_medium_small_square": "◾️", + "phone": "☎️", + "telephone": "☎️", + "point_up": "☝️", + "star_and_crescent": "☪", + "peace_symbol": "☮", + "yin_yang": "☯", + "relaxed": "☺️", + "gemini": "♊️", + "cancer": "♋️", + "leo": "♌️", + "virgo": "♍️", + "libra": "♎️", + "scorpius": "♏️", + "recycle": "♻️", + "wheelchair": "♿️", + "atom_symbol": "⚛", + "fleur_de_lis": "⚜", + "warning": "⚠️", + "zap": "⚡️", + "white_circle": "⚪️", + "black_circle": "⚫️", + "coffin": "⚰", + "funeral_urn": "⚱", + "soccer": "⚽️", + "baseball": "⚾️", + "snowman": "⛄️", + "partly_sunny": "⛅️", + "thunder_cloud_and_rain": "⛈", + "ophiuchus": "⛎", + "pick": "⛏", + "helmet_with_white_cross": "⛑", + "chains": "⛓", + "no_entry": "⛔️", + "shinto_shrine": "⛩", + "church": "⛪️", + "mountain": "⛰", + "umbrella_on_ground": "⛱", + "fountain": "⛲️", + "golf": "⛳️", + "ferry": "⛴", + "boat": "⛵️", + "sailboat": "⛵️", + "skier": "⛷", + "ice_skate": "⛸", + "person_with_ball": "⛹", + "tent": "⛺️", + "fuelpump": "⛽️", + "fist": "✊", + "hand": "✋", + "raised_hand": "✋", + "v": "✌️", + "writing_hand": "✍", + "pencil2": "✏️", + "latin_cross": "✝", + "x": "❌", + "negative_squared_cross_mark": "❎", + "arrow_right": "➡️", + "curly_loop": "➰", + "loop": "➿", + "arrow_left": "⬅️", + "arrow_up": "⬆️", + "arrow_down": "⬇️", + "black_large_square": "⬛️", + "white_large_square": "⬜️", + "star": "⭐️", + "o": "⭕️", + "part_alternation_mark": "〽️", + "mahjong": "🀄️", + "black_joker": "🃏", + "a": "🅰️", + "b": "🅱️", + "o2": "🅾️", + "parking": "🅿️", + "ab": "🆎", + "cl": "🆑", + "cool": "🆒", + "free": "🆓", + "id": "🆔", + "new": "🆕", + "ng": "🆖", + "ok": "🆗", + "sos": "🆘", + "up": "🆙", + "vs": "🆚", + "koko": "🈁", + "sa": "🈂️", + "u7121": "🈚️", + "u6307": "🈯️", + "u7981": "🈲", + "u7a7a": "🈳", + "u5408": "🈴", + "u6e80": "🈵", + "u6709": "🈶", + "u6708": "🈷️", + "u7533": "🈸", + "u5272": "🈹", + "u55b6": "🈺", + "ideograph_advantage": "🉐", + "accept": "🉑", + "cyclone": "🌀", + "foggy": "🌁", + "closed_umbrella": "🌂", + "night_with_stars": "🌃", + "sunrise_over_mountains": "🌄", + "sunrise": "🌅", + "city_sunset": "🌆", + "city_sunrise": "🌇", + "rainbow": "🌈", + "bridge_at_night": "🌉", + "ocean": "🌊", + "volcano": "🌋", + "milky_way": "🌌", + "earth_africa": "🌍", + "earth_americas": "🌎", + "earth_asia": "🌏", + "globe_with_meridians": "🌐", + "new_moon": "🌑", + "waxing_crescent_moon": "🌒", + "first_quarter_moon": "🌓", + "moon": "🌔", + "waxing_gibbous_moon": "🌔", + "full_moon": "🌕", + "waning_gibbous_moon": "🌖", + "last_quarter_moon": "🌗", + "waning_crescent_moon": "🌘", + "crescent_moon": "🌙", + "new_moon_with_face": "🌚", + "first_quarter_moon_with_face": "🌛", + "last_quarter_moon_with_face": "🌜", + "full_moon_with_face": "🌝", + "sun_with_face": "🌞", + "star2": "🌟", + "stars": "🌠", + "thermometer": "🌡", + "mostly_sunny": "🌤", + "sun_small_cloud": "🌤", + "barely_sunny": "🌥", + "sun_behind_cloud": "🌥", + "partly_sunny_rain": "🌦", + "sun_behind_rain_cloud": "🌦", + "rain_cloud": "🌧", + "snow_cloud": "🌨", + "lightning": "🌩", + "lightning_cloud": "🌩", + "tornado": "🌪", + "tornado_cloud": "🌪", + "fog": "🌫", + "wind_blowing_face": "🌬", + "hotdog": "🌭", + "taco": "🌮", + "burrito": "🌯", + "chestnut": "🌰", + "seedling": "🌱", + "evergreen_tree": "🌲", + "deciduous_tree": "🌳", + "palm_tree": "🌴", + "cactus": "🌵", + "hot_pepper": "🌶", + "tulip": "🌷", + "cherry_blossom": "🌸", + "rose": "🌹", + "hibiscus": "🌺", + "sunflower": "🌻", + "blossom": "🌼", + "corn": "🌽", + "ear_of_rice": "🌾", + "herb": "🌿", + "four_leaf_clover": "🍀", + "maple_leaf": "🍁", + "fallen_leaf": "🍂", + "leaves": "🍃", + "mushroom": "🍄", + "tomato": "🍅", + "eggplant": "🍆", + "grapes": "🍇", + "melon": "🍈", + "watermelon": "🍉", + "tangerine": "🍊", + "lemon": "🍋", + "banana": "🍌", + "pineapple": "🍍", + "apple": "🍎", + "green_apple": "🍏", + "pear": "🍐", + "peach": "🍑", + "cherries": "🍒", + "strawberry": "🍓", + "hamburger": "🍔", + "pizza": "🍕", + "meat_on_bone": "🍖", + "poultry_leg": "🍗", + "rice_cracker": "🍘", + "rice_ball": "🍙", + "rice": "🍚", + "curry": "🍛", + "ramen": "🍜", + "spaghetti": "🍝", + "bread": "🍞", + "fries": "🍟", + "sweet_potato": "🍠", + "dango": "🍡", + "oden": "🍢", + "sushi": "🍣", + "fried_shrimp": "🍤", + "fish_cake": "🍥", + "icecream": "🍦", + "shaved_ice": "🍧", + "ice_cream": "🍨", + "doughnut": "🍩", + "cookie": "🍪", + "chocolate_bar": "🍫", + "candy": "🍬", + "lollipop": "🍭", + "custard": "🍮", + "honey_pot": "🍯", + "cake": "🍰", + "bento": "🍱", + "stew": "🍲", + "egg": "🍳", + "fork_and_knife": "🍴", + "tea": "🍵", + "sake": "🍶", + "wine_glass": "🍷", + "cocktail": "🍸", + "tropical_drink": "🍹", + "beer": "🍺", + "beers": "🍻", + "baby_bottle": "🍼", + "knife_fork_plate": "🍽", + "champagne": "🍾", + "popcorn": "🍿", + "ribbon": "🎀", + "gift": "🎁", + "birthday": "🎂", + "jack_o_lantern": "🎃", + "christmas_tree": "🎄", + "santa": "🎅", + "fireworks": "🎆", + "sparkler": "🎇", + "balloon": "🎈", + "tada": "🎉", + "confetti_ball": "🎊", + "tanabata_tree": "🎋", + "crossed_flags": "🎌", + "bamboo": "🎍", + "dolls": "🎎", + "flags": "🎏", + "wind_chime": "🎐", + "rice_scene": "🎑", + "school_satchel": "🎒", + "mortar_board": "🎓", + "medal": "🎖", + "reminder_ribbon": "🎗", + "studio_microphone": "🎙", + "level_slider": "🎚", + "control_knobs": "🎛", + "film_frames": "🎞", + "admission_tickets": "🎟", + "carousel_horse": "🎠", + "ferris_wheel": "🎡", + "roller_coaster": "🎢", + "fishing_pole_and_fish": "🎣", + "microphone": "🎤", + "movie_camera": "🎥", + "cinema": "🎦", + "headphones": "🎧", + "art": "🎨", + "tophat": "🎩", + "circus_tent": "🎪", + "ticket": "🎫", + "clapper": "🎬", + "performing_arts": "🎭", + "video_game": "🎮", + "dart": "🎯", + "slot_machine": "🎰", + "8ball": "🎱", + "game_die": "🎲", + "bowling": "🎳", + "flower_playing_cards": "🎴", + "musical_note": "🎵", + "notes": "🎶", + "saxophone": "🎷", + "guitar": "🎸", + "musical_keyboard": "🎹", + "trumpet": "🎺", + "violin": "🎻", + "musical_score": "🎼", + "running_shirt_with_sash": "🎽", + "tennis": "🎾", + "ski": "🎿", + "basketball": "🏀", + "checkered_flag": "🏁", + "snowboarder": "🏂", + "runner": "🏃", + "running": "🏃", + "surfer": "🏄", + "sports_medal": "🏅", + "trophy": "🏆", + "horse_racing": "🏇", + "football": "🏈", + "rugby_football": "🏉", + "swimmer": "🏊", + "weight_lifter": "🏋", + "golfer": "🏌", + "racing_motorcycle": "🏍", + "racing_car": "🏎", + "cricket_bat_and_ball": "🏏", + "volleyball": "🏐", + "field_hockey_stick_and_ball": "🏑", + "ice_hockey_stick_and_puck": "🏒", + "table_tennis_paddle_and_ball": "🏓", + "snow_capped_mountain": "🏔", + "camping": "🏕", + "beach_with_umbrella": "🏖", + "building_construction": "🏗", + "house_buildings": "🏘", + "cityscape": "🏙", + "derelict_house_building": "🏚", + "classical_building": "🏛", + "desert": "🏜", + "desert_island": "🏝", + "national_park": "🏞", + "stadium": "🏟", + "house": "🏠", + "house_with_garden": "🏡", + "office": "🏢", + "post_office": "🏣", + "european_post_office": "🏤", + "hospital": "🏥", + "bank": "🏦", + "atm": "🏧", + "hotel": "🏨", + "love_hotel": "🏩", + "convenience_store": "🏪", + "school": "🏫", + "department_store": "🏬", + "factory": "🏭", + "izakaya_lantern": "🏮", + "lantern": "🏮", + "japanese_castle": "🏯", + "european_castle": "🏰", + "waving_white_flag": "🏳", + "waving_black_flag": "🏴", + "rosette": "🏵", + "label": "🏷", + "badminton_racquet_and_shuttlecock": "🏸", + "bow_and_arrow": "🏹", + "amphora": "🏺", + "skin-tone-2": "🏻", + "skin-tone-3": "🏼", + "skin-tone-4": "🏽", + "skin-tone-5": "🏾", + "skin-tone-6": "🏿", + "rat": "🐀", + "mouse2": "🐁", + "ox": "🐂", + "water_buffalo": "🐃", + "cow2": "🐄", + "tiger2": "🐅", + "leopard": "🐆", + "rabbit2": "🐇", + "cat2": "🐈", + "dragon": "🐉", + "crocodile": "🐊", + "whale2": "🐋", + "snail": "🐌", + "snake": "🐍", + "racehorse": "🐎", + "ram": "🐏", + "goat": "🐐", + "sheep": "🐑", + "monkey": "🐒", + "rooster": "🐓", + "chicken": "🐔", + "dog2": "🐕", + "pig2": "🐖", + "boar": "🐗", + "elephant": "🐘", + "octopus": "🐙", + "shell": "🐚", + "bug": "🐛", + "ant": "🐜", + "bee": "🐝", + "honeybee": "🐝", + "beetle": "🐞", + "fish": "🐟", + "tropical_fish": "🐠", + "blowfish": "🐡", + "turtle": "🐢", + "hatching_chick": "🐣", + "baby_chick": "🐤", + "hatched_chick": "🐥", + "bird": "🐦", + "penguin": "🐧", + "koala": "🐨", + "poodle": "🐩", + "dromedary_camel": "🐪", + "camel": "🐫", + "dolphin": "🐬", + "flipper": "🐬", + "mouse": "🐭", + "cow": "🐮", + "tiger": "🐯", + "rabbit": "🐰", + "cat": "🐱", + "dragon_face": "🐲", + "whale": "🐳", + "horse": "🐴", + "monkey_face": "🐵", + "dog": "🐶", + "pig": "🐷", + "frog": "🐸", + "hamster": "🐹", + "wolf": "🐺", + "bear": "🐻", + "panda_face": "🐼", + "pig_nose": "🐽", + "feet": "🐾", + "paw_prints": "🐾", + "chipmunk": "🐿", + "eyes": "👀", + "eye": "👁", + "ear": "👂", + "nose": "👃", + "lips": "👄", + "tongue": "👅", + "point_up_2": "👆", + "point_down": "👇", + "point_left": "👈", + "point_right": "👉", + "facepunch": "👊", + "punch": "👊", + "wave": "👋", + "ok_hand": "👌", + "+1": "👍", + "thumbsup": "👍", + "-1": "👎", + "thumbsdown": "👎", + "clap": "👏", + "open_hands": "👐", + "crown": "👑", + "womans_hat": "👒", + "eyeglasses": "👓", + "necktie": "👔", + "shirt": "👕", + "tshirt": "👕", + "jeans": "👖", + "dress": "👗", + "kimono": "👘", + "bikini": "👙", + "womans_clothes": "👚", + "purse": "👛", + "handbag": "👜", + "pouch": "👝", + "mans_shoe": "👞", + "shoe": "👞", + "athletic_shoe": "👟", + "high_heel": "👠", + "sandal": "👡", + "boot": "👢", + "footprints": "👣", + "bust_in_silhouette": "👤", + "busts_in_silhouette": "👥", + "boy": "👦", + "girl": "👧", + "man": "👨", + "woman": "👩", + "family": "👨‍👩‍👦", + "man-woman-boy": "👨‍👩‍👦", + "couple": "👫", + "man_and_woman_holding_hands": "👫", + "two_men_holding_hands": "👬", + "two_women_holding_hands": "👭", + "cop": "👮", + "dancers": "👯", + "bride_with_veil": "👰", + "person_with_blond_hair": "👱", + "man_with_gua_pi_mao": "👲", + "man_with_turban": "👳", + "older_man": "👴", + "older_woman": "👵", + "baby": "👶", + "construction_worker": "👷", + "princess": "👸", + "japanese_ogre": "👹", + "japanese_goblin": "👺", + "ghost": "👻", + "angel": "👼", + "alien": "👽", + "space_invader": "👾", + "imp": "👿", + "skull": "💀", + "information_desk_person": "💁", + "guardsman": "💂", + "dancer": "💃", + "lipstick": "💄", + "nail_care": "💅", + "massage": "💆", + "haircut": "💇", + "barber": "💈", + "syringe": "💉", + "pill": "💊", + "kiss": "💋", + "love_letter": "💌", + "ring": "💍", + "gem": "💎", + "couplekiss": "💏", + "bouquet": "💐", + "couple_with_heart": "💑", + "wedding": "💒", + "heartbeat": "💓", + "broken_heart": "💔", + "two_hearts": "💕", + "sparkling_heart": "💖", + "heartpulse": "💗", + "cupid": "💘", + "blue_heart": "💙", + "green_heart": "💚", + "yellow_heart": "💛", + "purple_heart": "💜", + "gift_heart": "💝", + "revolving_hearts": "💞", + "heart_decoration": "💟", + "diamond_shape_with_a_dot_inside": "💠", + "bulb": "💡", + "anger": "💢", + "bomb": "💣", + "zzz": "💤", + "boom": "💥", + "collision": "💥", + "sweat_drops": "💦", + "droplet": "💧", + "dash": "💨", + "hankey": "💩", + "poop": "💩", + "shit": "💩", + "muscle": "💪", + "dizzy": "💫", + "speech_balloon": "💬", + "thought_balloon": "💭", + "white_flower": "💮", + "moneybag": "💰", + "currency_exchange": "💱", + "heavy_dollar_sign": "💲", + "credit_card": "💳", + "yen": "💴", + "dollar": "💵", + "euro": "💶", + "pound": "💷", + "money_with_wings": "💸", + "chart": "💹", + "seat": "💺", + "computer": "💻", + "briefcase": "💼", + "minidisc": "💽", + "floppy_disk": "💾", + "cd": "💿", + "dvd": "📀", + "file_folder": "📁", + "open_file_folder": "📂", + "page_with_curl": "📃", + "page_facing_up": "📄", + "date": "📅", + "calendar": "📆", + "card_index": "📇", + "chart_with_upwards_trend": "📈", + "chart_with_downwards_trend": "📉", + "bar_chart": "📊", + "clipboard": "📋", + "pushpin": "📌", + "round_pushpin": "📍", + "paperclip": "📎", + "straight_ruler": "📏", + "triangular_ruler": "📐", + "bookmark_tabs": "📑", + "ledger": "📒", + "notebook": "📓", + "notebook_with_decorative_cover": "📔", + "closed_book": "📕", + "book": "📖", + "open_book": "📖", + "green_book": "📗", + "blue_book": "📘", + "orange_book": "📙", + "books": "📚", + "name_badge": "📛", + "scroll": "📜", + "memo": "📝", + "pencil": "📝", + "telephone_receiver": "📞", + "pager": "📟", + "fax": "📠", + "satellite": "🛰", + "loudspeaker": "📢", + "mega": "📣", + "outbox_tray": "📤", + "inbox_tray": "📥", + "package": "📦", + "e-mail": "📧", + "incoming_envelope": "📨", + "envelope_with_arrow": "📩", + "mailbox_closed": "📪", + "mailbox": "📫", + "mailbox_with_mail": "📬", + "mailbox_with_no_mail": "📭", + "postbox": "📮", + "postal_horn": "📯", + "newspaper": "📰", + "iphone": "📱", + "calling": "📲", + "vibration_mode": "📳", + "mobile_phone_off": "📴", + "no_mobile_phones": "📵", + "signal_strength": "📶", + "camera": "📷", + "camera_with_flash": "📸", + "video_camera": "📹", + "tv": "📺", + "radio": "📻", + "vhs": "📼", + "film_projector": "📽", + "prayer_beads": "📿", + "twisted_rightwards_arrows": "🔀", + "repeat": "🔁", + "repeat_one": "🔂", + "arrows_clockwise": "🔃", + "arrows_counterclockwise": "🔄", + "low_brightness": "🔅", + "high_brightness": "🔆", + "mute": "🔇", + "speaker": "🔈", + "sound": "🔉", + "loud_sound": "🔊", + "battery": "🔋", + "electric_plug": "🔌", + "mag": "🔍", + "mag_right": "🔎", + "lock_with_ink_pen": "🔏", + "closed_lock_with_key": "🔐", + "key": "🔑", + "lock": "🔒", + "unlock": "🔓", + "bell": "🔔", + "no_bell": "🔕", + "bookmark": "🔖", + "link": "🔗", + "radio_button": "🔘", + "back": "🔙", + "end": "🔚", + "on": "🔛", + "soon": "🔜", + "top": "🔝", + "underage": "🔞", + "keycap_ten": "🔟", + "capital_abcd": "🔠", + "abcd": "🔡", + "symbols": "🔣", + "abc": "🔤", + "fire": "🔥", + "flashlight": "🔦", + "wrench": "🔧", + "hammer": "🔨", + "nut_and_bolt": "🔩", + "hocho": "🔪", + "knife": "🔪", + "gun": "🔫", + "microscope": "🔬", + "telescope": "🔭", + "crystal_ball": "🔮", + "six_pointed_star": "🔯", + "beginner": "🔰", + "trident": "🔱", + "black_square_button": "🔲", + "white_square_button": "🔳", + "red_circle": "🔴", + "large_blue_circle": "🔵", + "large_orange_diamond": "🔶", + "large_blue_diamond": "🔷", + "small_orange_diamond": "🔸", + "small_blue_diamond": "🔹", + "small_red_triangle": "🔺", + "small_red_triangle_down": "🔻", + "arrow_up_small": "🔼", + "arrow_down_small": "🔽", + "om_symbol": "🕉", + "dove_of_peace": "🕊", + "kaaba": "🕋", + "mosque": "🕌", + "synagogue": "🕍", + "menorah_with_nine_branches": "🕎", + "clock1": "🕐", + "clock2": "🕑", + "clock3": "🕒", + "clock4": "🕓", + "clock5": "🕔", + "clock6": "🕕", + "clock7": "🕖", + "clock8": "🕗", + "clock9": "🕘", + "clock10": "🕙", + "clock11": "🕚", + "clock12": "🕛", + "clock130": "🕜", + "clock230": "🕝", + "clock330": "🕞", + "clock430": "🕟", + "clock530": "🕠", + "clock630": "🕡", + "clock730": "🕢", + "clock830": "🕣", + "clock930": "🕤", + "clock1030": "🕥", + "clock1130": "🕦", + "clock1230": "🕧", + "candle": "🕯", + "mantelpiece_clock": "🕰", + "hole": "🕳", + "man_in_business_suit_levitating": "🕴", + "sleuth_or_spy": "🕵", + "dark_sunglasses": "🕶", + "spider": "🕷", + "spider_web": "🕸", + "joystick": "🕹", + "linked_paperclips": "🖇", + "lower_left_ballpoint_pen": "🖊", + "lower_left_fountain_pen": "🖋", + "lower_left_paintbrush": "🖌", + "lower_left_crayon": "🖍", + "raised_hand_with_fingers_splayed": "🖐", + "middle_finger": "🖕", + "reversed_hand_with_middle_finger_extended": "🖕", + "spock-hand": "🖖", + "desktop_computer": "🖥", + "printer": "🖨", + "three_button_mouse": "🖱", + "trackball": "🖲", + "frame_with_picture": "🖼", + "card_index_dividers": "🗂", + "card_file_box": "🗃", + "file_cabinet": "🗄", + "wastebasket": "🗑", + "spiral_note_pad": "🗒", + "spiral_calendar_pad": "🗓", + "compression": "🗜", + "old_key": "🗝", + "rolled_up_newspaper": "🗞", + "dagger_knife": "🗡", + "speaking_head_in_silhouette": "🗣", + "left_speech_bubble": "🗨", + "right_anger_bubble": "🗯", + "ballot_box_with_ballot": "🗳", + "world_map": "🗺", + "mount_fuji": "🗻", + "tokyo_tower": "🗼", + "statue_of_liberty": "🗽", + "japan": "🗾", + "moyai": "🗿", + "grinning": "😀", + "grin": "😁", + "joy": "😂", + "smiley": "😃", + "smile": "😄", + "sweat_smile": "😅", + "laughing": "😆", + "satisfied": "😆", + "innocent": "😇", + "smiling_imp": "😈", + "wink": "😉", + "blush": "😊", + "yum": "😋", + "relieved": "😌", + "heart_eyes": "😍", + "sunglasses": "😎", + "smirk": "😏", + "neutral_face": "😐", + "expressionless": "😑", + "unamused": "😒", + "sweat": "😓", + "pensive": "😔", + "confused": "😕", + "confounded": "😖", + "kissing": "😗", + "kissing_heart": "😘", + "kissing_smiling_eyes": "😙", + "kissing_closed_eyes": "😚", + "stuck_out_tongue": "😛", + "stuck_out_tongue_winking_eye": "😜", + "stuck_out_tongue_closed_eyes": "😝", + "disappointed": "😞", + "worried": "😟", + "angry": "😠", + "rage": "😡", + "cry": "😢", + "persevere": "😣", + "triumph": "😤", + "disappointed_relieved": "😥", + "frowning": "😦", + "anguished": "😧", + "fearful": "😨", + "weary": "😩", + "sleepy": "😪", + "tired_face": "😫", + "grimacing": "😬", + "sob": "😭", + "open_mouth": "😮", + "hushed": "😯", + "cold_sweat": "😰", + "scream": "😱", + "astonished": "😲", + "flushed": "😳", + "sleeping": "😴", + "dizzy_face": "😵", + "no_mouth": "😶", + "mask": "😷", + "smile_cat": "😸", + "joy_cat": "😹", + "smiley_cat": "😺", + "heart_eyes_cat": "😻", + "smirk_cat": "😼", + "kissing_cat": "😽", + "pouting_cat": "😾", + "crying_cat_face": "😿", + "scream_cat": "🙀", + "slightly_frowning_face": "🙁", + "slightly_smiling_face": "🙂", + "upside_down_face": "🙃", + "face_with_rolling_eyes": "🙄", + "no_good": "🙅", + "ok_woman": "🙆", + "bow": "🙇", + "see_no_evil": "🙈", + "hear_no_evil": "🙉", + "speak_no_evil": "🙊", + "raising_hand": "🙋", + "raised_hands": "🙌", + "person_frowning": "🙍", + "person_with_pouting_face": "🙎", + "pray": "🙏", + "rocket": "🚀", + "helicopter": "🚁", + "steam_locomotive": "🚂", + "railway_car": "🚃", + "bullettrain_side": "🚄", + "bullettrain_front": "🚅", + "train2": "🚆", + "metro": "🚇", + "light_rail": "🚈", + "station": "🚉", + "tram": "🚊", + "train": "🚋", + "bus": "🚌", + "oncoming_bus": "🚍", + "trolleybus": "🚎", + "busstop": "🚏", + "minibus": "🚐", + "ambulance": "🚑", + "fire_engine": "🚒", + "police_car": "🚓", + "oncoming_police_car": "🚔", + "taxi": "🚕", + "oncoming_taxi": "🚖", + "car": "🚗", + "red_car": "🚗", + "oncoming_automobile": "🚘", + "blue_car": "🚙", + "truck": "🚚", + "articulated_lorry": "🚛", + "tractor": "🚜", + "monorail": "🚝", + "mountain_railway": "🚞", + "suspension_railway": "🚟", + "mountain_cableway": "🚠", + "aerial_tramway": "🚡", + "ship": "🚢", + "rowboat": "🚣", + "speedboat": "🚤", + "traffic_light": "🚥", + "vertical_traffic_light": "🚦", + "construction": "🚧", + "rotating_light": "🚨", + "triangular_flag_on_post": "🚩", + "door": "🚪", + "no_entry_sign": "🚫", + "smoking": "🚬", + "no_smoking": "🚭", + "put_litter_in_its_place": "🚮", + "do_not_litter": "🚯", + "potable_water": "🚰", + "non-potable_water": "🚱", + "bike": "🚲", + "no_bicycles": "🚳", + "bicyclist": "🚴", + "mountain_bicyclist": "🚵", + "walking": "🚶", + "no_pedestrians": "🚷", + "children_crossing": "🚸", + "mens": "🚹", + "womens": "🚺", + "restroom": "🚻", + "baby_symbol": "🚼", + "toilet": "🚽", + "wc": "🚾", + "shower": "🚿", + "bath": "🛀", + "bathtub": "🛁", + "passport_control": "🛂", + "customs": "🛃", + "baggage_claim": "🛄", + "left_luggage": "🛅", + "couch_and_lamp": "🛋", + "sleeping_accommodation": "🛌", + "shopping_bags": "🛍", + "bellhop_bell": "🛎", + "bed": "🛏", + "place_of_worship": "🛐", + "hammer_and_wrench": "🛠", + "shield": "🛡", + "oil_drum": "🛢", + "motorway": "🛣", + "railway_track": "🛤", + "motor_boat": "🛥", + "small_airplane": "🛩", + "airplane_departure": "🛫", + "airplane_arriving": "🛬", + "passenger_ship": "🛳", + "zipper_mouth_face": "🤐", + "money_mouth_face": "🤑", + "face_with_thermometer": "🤒", + "nerd_face": "🤓", + "thinking_face": "🤔", + "face_with_head_bandage": "🤕", + "robot_face": "🤖", + "hugging_face": "🤗", + "the_horns": "🤘", + "sign_of_the_horns": "🤘", + "crab": "🦀", + "lion_face": "🦁", + "scorpion": "🦂", + "turkey": "🦃", + "unicorn_face": "🦄", + "cheese_wedge": "🧀", + "hash": "#️⃣", + "keycap_star": "*⃣", + "zero": "0️⃣", + "one": "1️⃣", + "two": "2️⃣", + "three": "3️⃣", + "four": "4️⃣", + "five": "5️⃣", + "six": "6️⃣", + "seven": "7️⃣", + "eight": "8️⃣", + "nine": "9️⃣", + "flag-ac": "🇦🇨", + "flag-ad": "🇦🇩", + "flag-ae": "🇦🇪", + "flag-af": "🇦🇫", + "flag-ag": "🇦🇬", + "flag-ai": "🇦🇮", + "flag-al": "🇦🇱", + "flag-am": "🇦🇲", + "flag-ao": "🇦🇴", + "flag-aq": "🇦🇶", + "flag-ar": "🇦🇷", + "flag-as": "🇦🇸", + "flag-at": "🇦🇹", + "flag-au": "🇦🇺", + "flag-aw": "🇦🇼", + "flag-ax": "🇦🇽", + "flag-az": "🇦🇿", + "flag-ba": "🇧🇦", + "flag-bb": "🇧🇧", + "flag-bd": "🇧🇩", + "flag-be": "🇧🇪", + "flag-bf": "🇧🇫", + "flag-bg": "🇧🇬", + "flag-bh": "🇧🇭", + "flag-bi": "🇧🇮", + "flag-bj": "🇧🇯", + "flag-bl": "🇧🇱", + "flag-bm": "🇧🇲", + "flag-bn": "🇧🇳", + "flag-bo": "🇧🇴", + "flag-bq": "🇧🇶", + "flag-br": "🇧🇷", + "flag-bs": "🇧🇸", + "flag-bt": "🇧🇹", + "flag-bv": "🇧🇻", + "flag-bw": "🇧🇼", + "flag-by": "🇧🇾", + "flag-bz": "🇧🇿", + "flag-ca": "🇨🇦", + "flag-cc": "🇨🇨", + "flag-cd": "🇨🇩", + "flag-cf": "🇨🇫", + "flag-cg": "🇨🇬", + "flag-ch": "🇨🇭", + "flag-ci": "🇨🇮", + "flag-ck": "🇨🇰", + "flag-cl": "🇨🇱", + "flag-cm": "🇨🇲", + "flag-cn": "🇨🇳", + "cn": "🇨🇳", + "flag-co": "🇨🇴", + "flag-cp": "🇨🇵", + "flag-cr": "🇨🇷", + "flag-cu": "🇨🇺", + "flag-cv": "🇨🇻", + "flag-cw": "🇨🇼", + "flag-cx": "🇨🇽", + "flag-cy": "🇨🇾", + "flag-cz": "🇨🇿", + "flag-de": "🇩🇪", + "de": "🇩🇪", + "flag-dg": "🇩🇬", + "flag-dj": "🇩🇯", + "flag-dk": "🇩🇰", + "flag-dm": "🇩🇲", + "flag-do": "🇩🇴", + "flag-dz": "🇩🇿", + "flag-ea": "🇪🇦", + "flag-ec": "🇪🇨", + "flag-ee": "🇪🇪", + "flag-eg": "🇪🇬", + "flag-eh": "🇪🇭", + "flag-er": "🇪🇷", + "flag-es": "🇪🇸", + "es": "🇪🇸", + "flag-et": "🇪🇹", + "flag-eu": "🇪🇺", + "flag-fi": "🇫🇮", + "flag-fj": "🇫🇯", + "flag-fk": "🇫🇰", + "flag-fm": "🇫🇲", + "flag-fo": "🇫🇴", + "flag-fr": "🇫🇷", + "fr": "🇫🇷", + "flag-ga": "🇬🇦", + "flag-gb": "🇬🇧", + "gb": "🇬🇧", + "uk": "🇬🇧", + "flag-gd": "🇬🇩", + "flag-ge": "🇬🇪", + "flag-gf": "🇬🇫", + "flag-gg": "🇬🇬", + "flag-gh": "🇬🇭", + "flag-gi": "🇬🇮", + "flag-gl": "🇬🇱", + "flag-gm": "🇬🇲", + "flag-gn": "🇬🇳", + "flag-gp": "🇬🇵", + "flag-gq": "🇬🇶", + "flag-gr": "🇬🇷", + "flag-gs": "🇬🇸", + "flag-gt": "🇬🇹", + "flag-gu": "🇬🇺", + "flag-gw": "🇬🇼", + "flag-gy": "🇬🇾", + "flag-hk": "🇭🇰", + "flag-hm": "🇭🇲", + "flag-hn": "🇭🇳", + "flag-hr": "🇭🇷", + "flag-ht": "🇭🇹", + "flag-hu": "🇭🇺", + "flag-ic": "🇮🇨", + "flag-id": "🇮🇩", + "flag-ie": "🇮🇪", + "flag-il": "🇮🇱", + "flag-im": "🇮🇲", + "flag-in": "🇮🇳", + "flag-io": "🇮🇴", + "flag-iq": "🇮🇶", + "flag-ir": "🇮🇷", + "flag-is": "🇮🇸", + "flag-it": "🇮🇹", + "it": "🇮🇹", + "flag-je": "🇯🇪", + "flag-jm": "🇯🇲", + "flag-jo": "🇯🇴", + "flag-jp": "🇯🇵", + "jp": "🇯🇵", + "flag-ke": "🇰🇪", + "flag-kg": "🇰🇬", + "flag-kh": "🇰🇭", + "flag-ki": "🇰🇮", + "flag-km": "🇰🇲", + "flag-kn": "🇰🇳", + "flag-kp": "🇰🇵", + "flag-kr": "🇰🇷", + "kr": "🇰🇷", + "flag-kw": "🇰🇼", + "flag-ky": "🇰🇾", + "flag-kz": "🇰🇿", + "flag-la": "🇱🇦", + "flag-lb": "🇱🇧", + "flag-lc": "🇱🇨", + "flag-li": "🇱🇮", + "flag-lk": "🇱🇰", + "flag-lr": "🇱🇷", + "flag-ls": "🇱🇸", + "flag-lt": "🇱🇹", + "flag-lu": "🇱🇺", + "flag-lv": "🇱🇻", + "flag-ly": "🇱🇾", + "flag-ma": "🇲🇦", + "flag-mc": "🇲🇨", + "flag-md": "🇲🇩", + "flag-me": "🇲🇪", + "flag-mf": "🇲🇫", + "flag-mg": "🇲🇬", + "flag-mh": "🇲🇭", + "flag-mk": "🇲🇰", + "flag-ml": "🇲🇱", + "flag-mm": "🇲🇲", + "flag-mn": "🇲🇳", + "flag-mo": "🇲🇴", + "flag-mp": "🇲🇵", + "flag-mq": "🇲🇶", + "flag-mr": "🇲🇷", + "flag-ms": "🇲🇸", + "flag-mt": "🇲🇹", + "flag-mu": "🇲🇺", + "flag-mv": "🇲🇻", + "flag-mw": "🇲🇼", + "flag-mx": "🇲🇽", + "flag-my": "🇲🇾", + "flag-mz": "🇲🇿", + "flag-na": "🇳🇦", + "flag-nc": "🇳🇨", + "flag-ne": "🇳🇪", + "flag-nf": "🇳🇫", + "flag-ng": "🇳🇬", + "flag-ni": "🇳🇮", + "flag-nl": "🇳🇱", + "flag-no": "🇳🇴", + "flag-np": "🇳🇵", + "flag-nr": "🇳🇷", + "flag-nu": "🇳🇺", + "flag-nz": "🇳🇿", + "flag-om": "🇴🇲", + "flag-pa": "🇵🇦", + "flag-pe": "🇵🇪", + "flag-pf": "🇵🇫", + "flag-pg": "🇵🇬", + "flag-ph": "🇵🇭", + "flag-pk": "🇵🇰", + "flag-pl": "🇵🇱", + "flag-pm": "🇵🇲", + "flag-pn": "🇵🇳", + "flag-pr": "🇵🇷", + "flag-ps": "🇵🇸", + "flag-pt": "🇵🇹", + "flag-pw": "🇵🇼", + "flag-py": "🇵🇾", + "flag-qa": "🇶🇦", + "flag-re": "🇷🇪", + "flag-ro": "🇷🇴", + "flag-rs": "🇷🇸", + "flag-ru": "🇷🇺", + "ru": "🇷🇺", + "flag-rw": "🇷🇼", + "flag-sa": "🇸🇦", + "flag-sb": "🇸🇧", + "flag-sc": "🇸🇨", + "flag-sd": "🇸🇩", + "flag-se": "🇸🇪", + "flag-sg": "🇸🇬", + "flag-sh": "🇸🇭", + "flag-si": "🇸🇮", + "flag-sj": "🇸🇯", + "flag-sk": "🇸🇰", + "flag-sl": "🇸🇱", + "flag-sm": "🇸🇲", + "flag-sn": "🇸🇳", + "flag-so": "🇸🇴", + "flag-sr": "🇸🇷", + "flag-ss": "🇸🇸", + "flag-st": "🇸🇹", + "flag-sv": "🇸🇻", + "flag-sx": "🇸🇽", + "flag-sy": "🇸🇾", + "flag-sz": "🇸🇿", + "flag-ta": "🇹🇦", + "flag-tc": "🇹🇨", + "flag-td": "🇹🇩", + "flag-tf": "🇹🇫", + "flag-tg": "🇹🇬", + "flag-th": "🇹🇭", + "flag-tj": "🇹🇯", + "flag-tk": "🇹🇰", + "flag-tl": "🇹🇱", + "flag-tm": "🇹🇲", + "flag-tn": "🇹🇳", + "flag-to": "🇹🇴", + "flag-tr": "🇹🇷", + "flag-tt": "🇹🇹", + "flag-tv": "🇹🇻", + "flag-tw": "🇹🇼", + "flag-tz": "🇹🇿", + "flag-ua": "🇺🇦", + "flag-ug": "🇺🇬", + "flag-um": "🇺🇲", + "flag-us": "🇺🇸", + "us": "🇺🇸", + "flag-uy": "🇺🇾", + "flag-uz": "🇺🇿", + "flag-va": "🇻🇦", + "flag-vc": "🇻🇨", + "flag-ve": "🇻🇪", + "flag-vg": "🇻🇬", + "flag-vi": "🇻🇮", + "flag-vn": "🇻🇳", + "flag-vu": "🇻🇺", + "flag-wf": "🇼🇫", + "flag-ws": "🇼🇸", + "flag-xk": "🇽🇰", + "flag-ye": "🇾🇪", + "flag-yt": "🇾🇹", + "flag-za": "🇿🇦", + "flag-zm": "🇿🇲", + "flag-zw": "🇿🇼", + "man-man-boy": "👨‍👨‍👦", + "man-man-boy-boy": "👨‍👨‍👦‍👦", + "man-man-girl": "👨‍👨‍👧", + "man-man-girl-boy": "👨‍👨‍👧‍👦", + "man-man-girl-girl": "👨‍👨‍👧‍👧", + "man-woman-boy-boy": "👨‍👩‍👦‍👦", + "man-woman-girl": "👨‍👩‍👧", + "man-woman-girl-boy": "👨‍👩‍👧‍👦", + "man-woman-girl-girl": "👨‍👩‍👧‍👧", + "man-heart-man": "👨‍❤️‍👨", + "man-kiss-man": "👨‍❤️‍💋‍👨", + "woman-woman-boy": "👩‍👩‍👦", + "woman-woman-boy-boy": "👩‍👩‍👦‍👦", + "woman-woman-girl": "👩‍👩‍👧", + "woman-woman-girl-boy": "👩‍👩‍👧‍👦", + "woman-woman-girl-girl": "👩‍👩‍👧‍👧", + "woman-heart-woman": "👩‍❤️‍👩", + "woman-kiss-woman": "👩‍❤️‍💋‍👩" +}; \ No newline at end of file diff --git a/src/assets/index.d.ts b/src/assets/index.d.ts new file mode 100644 index 0000000000..855d9dc36f --- /dev/null +++ b/src/assets/index.d.ts @@ -0,0 +1,3 @@ +import { Assets } from './Assets'; +declare const _default: Assets; +export default _default; diff --git a/src/assets/index.js b/src/assets/index.js new file mode 100644 index 0000000000..d018064bda --- /dev/null +++ b/src/assets/index.js @@ -0,0 +1,9 @@ +import { Assets } from "./Assets"; +export default new Assets().loadAssetsGroup('', { + get emojis() { + return require("./emojis").emojis; + }, + get internal() { + return require("./internal").internal; + } +}); \ No newline at end of file diff --git a/src/assets/internal/index.d.ts b/src/assets/internal/index.d.ts new file mode 100644 index 0000000000..36b54779ec --- /dev/null +++ b/src/assets/internal/index.d.ts @@ -0,0 +1,4 @@ +export declare const internal: { + readonly icons: any; + readonly images: any; +}; diff --git a/src/assets/internal/index.js b/src/assets/internal/index.js new file mode 100644 index 0000000000..4a6e5a6b3e --- /dev/null +++ b/src/assets/internal/index.js @@ -0,0 +1,8 @@ +export const internal = { + get icons() { + return require("./icons").icons; + }, + get images() { + return require("./images").images; + } +}; \ No newline at end of file diff --git a/src/commons/Config.d.ts b/src/commons/Config.d.ts new file mode 100644 index 0000000000..bfbaea0dc3 --- /dev/null +++ b/src/commons/Config.d.ts @@ -0,0 +1,24 @@ +import { SchemeType } from '../style'; +interface ConfigOptions { + /** + * Should use platform colors for design tokens + */ + usePlatformColors?: boolean; + /** + * Whether to scheme from local storage + */ + useLocalScheme?: boolean; + /** + * The app's colors scheme (default | light | dark) + */ + appScheme?: SchemeType; +} +declare class Config { + usePlatformColors?: boolean; + useLocalScheme?: boolean; + appScheme: SchemeType; + constructor(); + setConfig(options: ConfigOptions): Promise; +} +declare const _default: Config; +export default _default; diff --git a/src/commons/Config.js b/src/commons/Config.js new file mode 100644 index 0000000000..1c760d919d --- /dev/null +++ b/src/commons/Config.js @@ -0,0 +1,15 @@ +class Config { + appScheme = 'light'; + constructor() { + this.setConfig({}); + } + async setConfig(options) { + const { + usePlatformColors = false, + appScheme = 'light' + } = options; + this.usePlatformColors = usePlatformColors; + this.appScheme = appScheme; + } +} +export default new Config(); \ No newline at end of file diff --git a/src/commons/Constants.d.ts b/src/commons/Constants.d.ts new file mode 100644 index 0000000000..66ccfc7fab --- /dev/null +++ b/src/commons/Constants.d.ts @@ -0,0 +1,54 @@ +export declare enum orientations { + PORTRAIT = "portrait", + LANDSCAPE = "landscape" +} +export interface Breakpoint { + breakpoint: number; + pageMargin: number; +} +declare let defaultMargin: number; +export declare function updateConstants(dimensions: any): void; +declare const constants: { + orientations: typeof orientations; + isAndroid: boolean; + isIOS: boolean; + isWeb: boolean; + getAndroidVersion: () => number | undefined; + readonly statusBarHeight: number; + isRTL: boolean; + readonly orientation: orientations; + readonly isLandscape: boolean; + readonly screenWidth: number; + readonly screenHeight: number; + readonly windowWidth: number; + readonly windowHeight: number; + readonly isSmallWindow: boolean; + readonly isSmallScreen: boolean; + readonly isShortScreen: boolean; + readonly isWideScreen: boolean; + readonly screenAspectRatio: number; + isTablet: boolean; + setBreakpoints(value: Breakpoint[], options?: { + defaultMargin: number; + }): void; + getPageMargins(): number; + getSafeAreaInsets: () => { + left: number; + right: number; + bottom: number; + top: number; + }; + readonly isIphoneX: boolean; + dimensionsEventListener: undefined; + addDimensionsEventListener: (callback: any) => import("react-native").EmitterSubscription; + removeDimensionsEventListener: (callback: any) => void; + readonly accessibility: { + isReduceMotionEnabled: boolean; + isScreenReaderEnabled: boolean; + }; + backspaceKey: string; + enterKey: string; + getFontScale: () => number; +}; +export default constants; +export declare const _reset: () => void; diff --git a/src/commons/Constants.js b/src/commons/Constants.js new file mode 100644 index 0000000000..e80ab55327 --- /dev/null +++ b/src/commons/Constants.js @@ -0,0 +1,186 @@ +import { Platform, Dimensions, NativeModules, I18nManager, AccessibilityInfo, StatusBar, PixelRatio } from 'react-native'; +export let orientations = /*#__PURE__*/function (orientations) { + orientations["PORTRAIT"] = "portrait"; + orientations["LANDSCAPE"] = "landscape"; + return orientations; +}({}); +function breakpointComparator(b1, b2) { + return b1.breakpoint - b2.breakpoint; +} +const isAndroid = Platform.OS === 'android'; +const isIOS = Platform.OS === 'ios'; +const isWeb = Platform.OS === 'web'; +let isTablet; +let statusBarHeight; +let screenHeight = Dimensions.get('screen').height; +let screenWidth = Dimensions.get('screen').width; +let windowHeight = Dimensions.get('window').height; +let windowWidth = Dimensions.get('window').width; +let breakpoints; +let defaultMargin = 0; +const isSubWindow = windowWidth < screenWidth; +isTablet = Platform.OS === 'ios' && Platform.isPad || getAspectRatio() < 1.6 && Math.max(screenWidth, screenHeight) >= 900; +function setStatusBarHeight() { + const { + StatusBarManager + } = NativeModules; + statusBarHeight = (StatusBar.currentHeight ?? StatusBarManager?.HEIGHT) || 0; + if (isIOS && StatusBarManager) { + // override guesstimate height with the actual height from StatusBarManager + StatusBarManager.getHeight(data => statusBarHeight = data.height); + } +} +function getAspectRatio() { + return screenWidth < screenHeight ? screenHeight / screenWidth : screenWidth / screenHeight; +} +function getOrientation(height, width) { + return width < height ? orientations.PORTRAIT : orientations.LANDSCAPE; +} +export function updateConstants(dimensions) { + screenHeight = dimensions.screen.height; + screenWidth = dimensions.screen.width; + windowWidth = dimensions.window.width; + windowHeight = dimensions.window.height; + setStatusBarHeight(); +} +const accessibility = { + isReduceMotionEnabled: false, + isScreenReaderEnabled: false +}; +function handleReduceMotionChanged(isReduceMotionEnabled) { + accessibility.isReduceMotionEnabled = isReduceMotionEnabled; +} +function handleScreenReaderChanged(isScreenReaderEnabled) { + accessibility.isScreenReaderEnabled = isScreenReaderEnabled; +} +AccessibilityInfo.addEventListener('reduceMotionChanged', handleReduceMotionChanged); +AccessibilityInfo.addEventListener('screenReaderChanged', handleScreenReaderChanged); +async function setAccessibility() { + accessibility.isReduceMotionEnabled = await AccessibilityInfo.isReduceMotionEnabled(); + accessibility.isScreenReaderEnabled = await AccessibilityInfo.isScreenReaderEnabled(); +} +setAccessibility(); +const constants = { + /* Platform */ + orientations, + isAndroid, + isIOS, + isWeb, + getAndroidVersion: () => { + return isAndroid ? parseInt(Platform.Version, 10) : undefined; + }, + /* Navigation */ + get statusBarHeight() { + return statusBarHeight; + }, + /* Layout */ + isRTL: I18nManager.isRTL, + get orientation() { + return getOrientation(windowHeight, windowWidth); + }, + get isLandscape() { + return getOrientation(windowHeight, windowWidth) === orientations.LANDSCAPE; + }, + get screenWidth() { + return screenWidth; + }, + get screenHeight() { + return screenHeight; + }, + get windowWidth() { + return windowWidth; + }, + get windowHeight() { + return windowHeight; + }, + get isSmallWindow() { + return windowWidth <= 340; + }, + get isSmallScreen() { + return screenWidth <= 340; + }, + get isShortScreen() { + return screenHeight <= 600; + }, + get isWideScreen() { + return isTablet && !isSubWindow || this.isLandscape; + }, + get screenAspectRatio() { + return getAspectRatio(); + }, + get isTablet() { + return isTablet; + }, + set isTablet(value) { + isTablet = value; + }, + setBreakpoints(value, options) { + breakpoints = value.sort(breakpointComparator); + if (options) { + defaultMargin = options.defaultMargin; + } + }, + getPageMargins() { + if (!breakpoints) { + return 0; + } + for (let i = breakpoints.length - 1; i >= 0; --i) { + if (windowWidth > breakpoints[i].breakpoint) { + return breakpoints[i].pageMargin; + } + } + return defaultMargin; + }, + getSafeAreaInsets: () => { + const orientation = getOrientation(screenHeight, screenWidth); + return orientation === orientations.LANDSCAPE ? { + left: 44, + right: 44, + bottom: 24, + top: 0 + } : { + left: 0, + right: 0, + bottom: 34, + top: 44 + }; + }, + /* Devices */ + get isIphoneX() { + return isIOS && + //@ts-ignore + !Platform.isPad && + //@ts-ignore + !Platform.isTVOS && (screenHeight >= 812 || screenWidth >= 812); + }, + /* Orientation */ + dimensionsEventListener: undefined, + addDimensionsEventListener: callback => { + return Dimensions.addEventListener('change', callback); + }, + /* Dimensions */ + removeDimensionsEventListener: callback => { + if (callback.remove) { + callback.remove(); + } + }, + /* Accessibility */ + get accessibility() { + return accessibility; + }, + /* Keyboard */ + backspaceKey: 'Backspace', + enterKey: 'Enter', + /* Font scale */ + getFontScale: PixelRatio.getFontScale +}; +setStatusBarHeight(); +Dimensions.addEventListener('change', updateConstants); +export default constants; + +// For tests +export const _reset = () => { + // @ts-ignore + breakpoints = undefined; + defaultMargin = 0; +}; \ No newline at end of file diff --git a/src/commons/UIComponent.d.ts b/src/commons/UIComponent.d.ts new file mode 100644 index 0000000000..4a4609d872 --- /dev/null +++ b/src/commons/UIComponent.d.ts @@ -0,0 +1,4 @@ +import React from 'react'; +export default class UIComponent

extends React.PureComponent { + static defaultProps: any; +} diff --git a/src/commons/UIComponent.js b/src/commons/UIComponent.js new file mode 100644 index 0000000000..7956a4d519 --- /dev/null +++ b/src/commons/UIComponent.js @@ -0,0 +1,5 @@ +import React from 'react'; + +// For use of applying dynamic context over all components + +export default class UIComponent extends React.PureComponent {} \ No newline at end of file diff --git a/src/commons/__tests__/constants.spec.js b/src/commons/__tests__/constants.spec.js new file mode 100644 index 0000000000..5bf54c8e49 --- /dev/null +++ b/src/commons/__tests__/constants.spec.js @@ -0,0 +1,202 @@ +import { default as Constants, updateConstants, _reset } from "../Constants"; +describe('Constants', () => { + beforeEach(() => { + _reset(); + }); + describe('Breakpoints and Page Margins', () => { + it('getPageMargins without init should return 0', () => { + expect(Constants.getPageMargins()).toBe(0); + }); + it('getPageMargins with one breakpoint', () => { + const original = { + screen: { + width: Constants.screenWidth, + height: Constants.screenHeight + }, + window: { + width: Constants.windowWidth, + height: Constants.windowHeight + } + }; + updateConstants({ + screen: { + width: 50, + height: 50 + }, + window: { + width: 50, + height: 50 + } + }); + Constants.setBreakpoints([{ + breakpoint: 100, + pageMargin: 5 + }]); + expect(Constants.getPageMargins()).toBe(0); + updateConstants(original); + expect(Constants.getPageMargins()).toBe(5); + }); + it('getPageMargins with one breakpoint and a default', () => { + const original = { + screen: { + width: Constants.screenWidth, + height: Constants.screenHeight + }, + window: { + width: Constants.windowWidth, + height: Constants.windowHeight + } + }; + updateConstants({ + screen: { + width: 50, + height: 50 + }, + window: { + width: 50, + height: 50 + } + }); + Constants.setBreakpoints([{ + breakpoint: 100, + pageMargin: 5 + }], { + defaultMargin: 3 + }); + expect(Constants.getPageMargins()).toBe(3); + updateConstants(original); + expect(Constants.getPageMargins()).toBe(5); + }); + it('getPageMargins with three breakpoints', () => { + const original = { + screen: { + width: Constants.screenWidth, + height: Constants.screenHeight + }, + window: { + width: Constants.windowWidth, + height: Constants.windowHeight + } + }; + updateConstants({ + screen: { + width: 50, + height: 50 + }, + window: { + width: 50, + height: 50 + } + }); + Constants.setBreakpoints([{ + breakpoint: 100, + pageMargin: 5 + }, { + breakpoint: 1000, + pageMargin: 10 + }]); + expect(Constants.getPageMargins()).toBe(0); + updateConstants({ + screen: { + width: 1200, + height: 1200 + }, + window: { + width: 1200, + height: 1200 + } + }); + expect(Constants.getPageMargins()).toBe(10); + updateConstants(original); + expect(Constants.getPageMargins()).toBe(5); + }); + it('getPageMargins with three breakpoints and a default', () => { + const original = { + screen: { + width: Constants.screenWidth, + height: Constants.screenHeight + }, + window: { + width: Constants.windowWidth, + height: Constants.windowHeight + } + }; + updateConstants({ + screen: { + width: 50, + height: 50 + }, + window: { + width: 50, + height: 50 + } + }); + Constants.setBreakpoints([{ + breakpoint: 100, + pageMargin: 5 + }, { + breakpoint: 1000, + pageMargin: 10 + }], { + defaultMargin: 3 + }); + expect(Constants.getPageMargins()).toBe(3); + updateConstants({ + screen: { + width: 1200, + height: 1200 + }, + window: { + width: 1200, + height: 1200 + } + }); + expect(Constants.getPageMargins()).toBe(10); + updateConstants(original); + expect(Constants.getPageMargins()).toBe(5); + }); + it('setBreakpoints should arrange input in order', () => { + const original = { + screen: { + width: Constants.screenWidth, + height: Constants.screenHeight + }, + window: { + width: Constants.windowWidth, + height: Constants.windowHeight + } + }; + updateConstants({ + screen: { + width: 50, + height: 50 + }, + window: { + width: 50, + height: 50 + } + }); + Constants.setBreakpoints([{ + breakpoint: 1000, + pageMargin: 10 + }, { + breakpoint: 100, + pageMargin: 5 + }]); + expect(Constants.getPageMargins()).toBe(0); + updateConstants({ + screen: { + width: 1200, + height: 1200 + }, + window: { + width: 1200, + height: 1200 + } + }); + expect(Constants.getPageMargins()).toBe(10); + updateConstants(original); + expect(Constants.getPageMargins()).toBe(5); + }); + }); +}); \ No newline at end of file diff --git a/src/commons/asBaseComponent.d.ts b/src/commons/asBaseComponent.d.ts new file mode 100644 index 0000000000..c95ccdc8e7 --- /dev/null +++ b/src/commons/asBaseComponent.d.ts @@ -0,0 +1,15 @@ +import React from 'react'; +import * as Modifiers from './modifiers'; +export interface BaseComponentInjectedProps { + /** + * All generated styles from the modifiers props + */ + modifiers: ReturnType; +} +export interface AsBaseComponentOptions { + ignoreModifiers?: boolean; + ignoreTheme?: boolean; + modifiersOptions?: Modifiers.ModifiersOptions; +} +declare function asBaseComponent(WrappedComponent: React.ComponentType, options?: AsBaseComponentOptions): React.ForwardRefExoticComponent & React.RefAttributes> & STATICS; +export default asBaseComponent; diff --git a/src/commons/asBaseComponent.js b/src/commons/asBaseComponent.js new file mode 100644 index 0000000000..7732ef7b84 --- /dev/null +++ b/src/commons/asBaseComponent.js @@ -0,0 +1,65 @@ +import React from 'react'; +import hoistStatics from 'hoist-non-react-statics'; +import * as Modifiers from "./modifiers"; +import { Scheme, ThemeManager } from "../style"; +import forwardRef from "./forwardRef"; +import UIComponent from "./UIComponent"; +const EMPTY_MODIFIERS = {}; +const colorScheme = Scheme.getSchemeType(); +function asBaseComponent(WrappedComponent, options = {}) { + class BaseComponent extends UIComponent { + state = { + error: false, + colorScheme + }; + componentDidMount() { + Scheme.addChangeListener(this.appearanceListener); + } + componentWillUnmount() { + Scheme.removeChangeListener(this.appearanceListener); + } + appearanceListener = colorScheme => { + // iOS 13 and above will trigger this call with the wrong colorScheme value. So just ignore returned colorScheme for now + // https://github.com/facebook/react-native/issues/28525 + // this.setState({colorScheme: Appearance.getColorScheme()}); + if (this.state.colorScheme !== colorScheme) { + this.setState({ + colorScheme + }); + } + }; + static getThemeProps = (props, context) => { + return Modifiers.getThemeProps.call(WrappedComponent, props, context); + }; + static getDerivedStateFromError(error) { + UIComponent.defaultProps?.onError?.(error, WrappedComponent.defaultProps); + return { + error: true + }; + } + render() { + const themeProps = options.ignoreTheme ? this.props : BaseComponent.getThemeProps(this.props, this.context); + const modifiers = options.ignoreModifiers ? EMPTY_MODIFIERS : Modifiers.generateModifiersStyle(options.modifiersOptions, themeProps); + // TODO: omit original modifiers props (left, right, flex, etc..) + // Because they throws an error when being passed to RNView on Android + // @ts-expect-error + const { + forwardedRef, + ...others + } = themeProps; + return this.state.error && UIComponent.defaultProps?.renderError || ; + } + } + + // Statics + hoistStatics(BaseComponent, WrappedComponent); + BaseComponent.displayName = WrappedComponent.displayName; + BaseComponent.propTypes = WrappedComponent.propTypes; + BaseComponent.defaultProps = WrappedComponent.defaultProps; + const ThemeContext = ThemeManager.getThemeContext(); + if (ThemeContext) { + BaseComponent.contextType = ThemeContext; + } + return forwardRef(BaseComponent); +} +export default asBaseComponent; \ No newline at end of file diff --git a/src/commons/baseComponent.d.ts b/src/commons/baseComponent.d.ts new file mode 100644 index 0000000000..6b8c9b3534 --- /dev/null +++ b/src/commons/baseComponent.d.ts @@ -0,0 +1,2 @@ +import { ComponentType } from 'react'; +export default function baseComponent(usePure: boolean): ComponentType; diff --git a/src/commons/baseComponent.js b/src/commons/baseComponent.js new file mode 100644 index 0000000000..ef64104cf2 --- /dev/null +++ b/src/commons/baseComponent.js @@ -0,0 +1,138 @@ +import _isEmpty from "lodash/isEmpty"; +import _find from "lodash/find"; +import _isEqual from "lodash/isEqual"; +import _filter from "lodash/filter"; +import _keys from "lodash/keys"; +import _union from "lodash/union"; +import _includes from "lodash/includes"; +import _pickBy from "lodash/pickBy"; +import _pick from "lodash/pick"; +import React from 'react'; +// import PropTypes from 'prop-types'; +import { StyleSheet } from 'react-native'; +import { Colors } from "../style"; +import * as Modifiers from "./modifiers"; +export default function baseComponent(usePure) { + const parent = usePure ? React.PureComponent : React.Component; + class BaseComponent extends parent { + // static propTypes = { + // ..._.mapValues(Typography, () => PropTypes.bool), + // ..._.mapValues(Colors, () => PropTypes.bool), + // useNativeDriver: PropTypes.bool, + // }; + + static extractOwnProps = Modifiers.extractOwnProps; + constructor(props) { + super(props); + if (!this.styles) { + this.generateStyles(); + } + this.state = { + ...this.buildStyleOutOfModifiers() + }; + } + + // TODO: remove this after migrating all components to use asBaseComponent HOC + UNSAFE_componentWillReceiveProps(nextProps) { + this.updateModifiers(this.getThemeProps(), nextProps); + } + + // TODO: stop using this and remove it + getSnippet() { + return null; + } + generateStyles() { + this.styles = StyleSheet.create({}); + } + getThemeProps = Modifiers.getThemeProps; + extractAccessibilityProps = Modifiers.extractAccessibilityProps; + extractTypographyValue() { + return Modifiers.extractTypographyValue(this.getThemeProps()); + } + extractColorValue = () => Modifiers.extractColorValue(this.getThemeProps()); + extractAnimationProps() { + return _pick(this.getThemeProps(), ['animation', 'duration', 'delay', 'direction', 'easing', 'iterationCount', 'transition', 'onAnimationBegin', 'onAnimationEnd', 'useNativeDriver']); + } + extractModifierProps() { + return Modifiers.extractModifierProps(this.getThemeProps()); + } + + // TODO: stop using this and remove it + extractContainerStyle(props) { + let containerStyle = {}; + if (props.containerStyle) { + containerStyle = _pickBy(props.containerStyle, (_value, key) => { + return key.includes('margin') || _includes(['alignSelf', 'transform'], key); + }); + } + return containerStyle; + } + updateModifiers(currentProps, nextProps) { + const ignoredKeys = ['children', 'forwardedRef', 'style', 'testID']; + const allKeys = _union([..._keys(currentProps), ..._keys(nextProps)]).filter(key => !ignoredKeys.includes(key)); + const changedKeys = _filter(allKeys, key => !_isEqual(currentProps[key], nextProps[key])); + const options = {}; + if (_find(changedKeys, key => Modifiers.FLEX_KEY_PATTERN.test(key))) { + options.flex = true; + } + if (_find(changedKeys, key => Modifiers.PADDING_KEY_PATTERN.test(key))) { + options.paddings = true; + } + if (_find(changedKeys, key => Modifiers.MARGIN_KEY_PATTERN.test(key))) { + options.margins = true; + } + if (_find(changedKeys, key => Modifiers.ALIGNMENT_KEY_PATTERN.test(key))) { + options.alignments = true; + } + if (_find(changedKeys, key => Colors.getBackgroundKeysPattern().test(key))) { + options.backgroundColor = true; + } + if (!_isEmpty(options)) { + this.setState({ + ...this.buildStyleOutOfModifiers(options, nextProps) + }); + } + } + buildStyleOutOfModifiers(options = { + backgroundColor: true, + borderRadius: true, + paddings: true, + margins: true, + alignments: true, + flex: true + }, props = this.getThemeProps()) { + const style = {}; + if (options.backgroundColor) { + style.backgroundColor = Modifiers.extractBackgroundColorValue(props); + } + if (options.borderRadius) { + style.borderRadius = Modifiers.extractBorderRadiusValue(props); + } + if (options.paddings) { + style.paddings = Modifiers.extractPaddingValues(props); + } + if (options.margins) { + style.margins = Modifiers.extractMarginValues(props); + } + if (options.alignments) { + style.alignments = Modifiers.extractAlignmentsValues(props); + } + if (options.flex) { + style.flexStyle = Modifiers.extractFlexStyle(props); + } + return style; + } + + // TODO: stop using this and remove it + // extractTextProps(props) { + // return _.pick(props, [..._.keys(Typography), ..._.keys(Colors), 'color']); + // } + + // React Native Methods + setRef = r => this.view = r; + getRef = () => this.view; + measureInWindow = (...args) => this.getRef().measureInWindow(...args); + measure = (...args) => this.getRef().measure(...args); // TODO: do we need this + } + return BaseComponent; +} \ No newline at end of file diff --git a/src/commons/forwardRef.d.ts b/src/commons/forwardRef.d.ts new file mode 100644 index 0000000000..4373cc2bf8 --- /dev/null +++ b/src/commons/forwardRef.d.ts @@ -0,0 +1,8 @@ +import React, { ComponentType, ForwardedRef } from 'react'; +export interface ForwardRefInjectedProps { + /** + * The forwarded ref of the containing element + */ + forwardedRef: ForwardedRef; +} +export default function forwardRef(WrappedComponent: ComponentType

>): React.ForwardRefExoticComponent & React.RefAttributes> & STATICS; diff --git a/src/commons/forwardRef.js b/src/commons/forwardRef.js new file mode 100644 index 0000000000..739770c561 --- /dev/null +++ b/src/commons/forwardRef.js @@ -0,0 +1,18 @@ +import React from 'react'; +import hoistStatics from 'hoist-non-react-statics'; +export default function forwardRef(WrappedComponent) { + function forwardRef(props, ref) { + return ; + } + + // @ts-expect-error + const ForwardedComponent = React.forwardRef(forwardRef); + hoistStatics(ForwardedComponent, WrappedComponent); + //@ts-ignore + ForwardedComponent.displayName = WrappedComponent.displayName; + //@ts-ignore + ForwardedComponent.propTypes = WrappedComponent.propTypes; + //@ts-ignore + ForwardedComponent.defaultProps = WrappedComponent.defaultProps; + return ForwardedComponent; +} \ No newline at end of file diff --git a/src/commons/modifiers.d.ts b/src/commons/modifiers.d.ts new file mode 100644 index 0000000000..6887581ea9 --- /dev/null +++ b/src/commons/modifiers.d.ts @@ -0,0 +1,117 @@ +import _ from 'lodash'; +import { DesignTokens } from '../style'; +import { BorderRadiusesLiterals } from '../style/borderRadiuses'; +import TypographyPresets from '../style/typographyPresets'; +import { colorsPalette } from '../style/colorsPalette'; +import type { Dictionary } from '../typings/common'; +export declare const FLEX_KEY_PATTERN: RegExp; +export declare const PADDING_KEY_PATTERN: RegExp; +export declare const MARGIN_KEY_PATTERN: RegExp; +export declare const ALIGNMENT_KEY_PATTERN: RegExp; +export declare const POSITION_KEY_PATTERN: RegExp; +export declare const GAP_KEY_PATTERN: RegExp; +export interface AlteredOptions { + flex?: boolean; + alignments?: boolean; + paddings?: boolean; + margins?: boolean; + backgroundColor?: boolean; + position?: boolean; +} +export interface ExtractedStyle { + color?: ReturnType; + typography?: ReturnType; + backgroundColor?: ReturnType; + borderRadius?: ReturnType; + paddings?: ReturnType; + margins?: ReturnType; + alignments?: ReturnType; + flexStyle?: ReturnType; + positionStyle?: ReturnType; + gap?: ReturnType; +} +export type ModifiersOptions = { + color?: boolean; + typography?: boolean; + backgroundColor?: boolean; + borderRadius?: boolean; + paddings?: boolean; + margins?: boolean; + alignments?: boolean; + flex?: boolean; + position?: boolean; + gap?: boolean; +}; +declare const PADDING_VARIATIONS: { + readonly padding: "padding"; + readonly paddingL: "paddingLeft"; + readonly paddingT: "paddingTop"; + readonly paddingR: "paddingRight"; + readonly paddingB: "paddingBottom"; + readonly paddingH: "paddingHorizontal"; + readonly paddingV: "paddingVertical"; +}; +declare const MARGIN_VARIATIONS: { + readonly margin: "margin"; + readonly marginL: "marginLeft"; + readonly marginT: "marginTop"; + readonly marginR: "marginRight"; + readonly marginB: "marginBottom"; + readonly marginH: "marginHorizontal"; + readonly marginV: "marginVertical"; +}; +declare const STYLE_KEY_CONVERTERS: { + readonly flex: "flex"; + readonly flexG: "flexGrow"; + readonly flexS: "flexShrink"; +}; +export type PaddingLiterals = keyof typeof PADDING_VARIATIONS; +export type NativePaddingKeyType = (typeof PADDING_VARIATIONS)[PaddingLiterals]; +export type MarginLiterals = keyof typeof MARGIN_VARIATIONS; +export type NativeMarginModifierKeyType = (typeof MARGIN_VARIATIONS)[MarginLiterals]; +export type FlexLiterals = keyof typeof STYLE_KEY_CONVERTERS; +export type NativeFlexModifierKeyType = (typeof STYLE_KEY_CONVERTERS)[FlexLiterals]; +export type ColorLiterals = keyof (typeof colorsPalette & typeof DesignTokens); +export type TypographyLiterals = keyof typeof TypographyPresets; +export type BorderRadiusLiterals = keyof typeof BorderRadiusesLiterals; +export type AlignmentLiterals = 'row' | 'spread' | 'center' | 'centerH' | 'centerV' | 'left' | 'right' | 'top' | 'bottom'; +export type PositionLiterals = 'absF' | 'absL' | 'absR' | 'absT' | 'absB' | 'absV' | 'absH'; +export type GapLiterals = 'gap'; +export type Modifier = Partial>; +export type CustomModifier = { + [key: string]: boolean; +}; +export type TypographyModifiers = Modifier | CustomModifier; +export type ColorsModifiers = Modifier | CustomModifier; +export type BackgroundColorModifier = Modifier<`bg-${ColorLiterals}`>; +export type AlignmentModifiers = Modifier; +export type PositionModifiers = Modifier; +export type PaddingModifiers = Modifier; +export type MarginModifiers = Modifier; +export type FlexModifiers = Modifier; +export type BorderRadiusModifiers = Modifier; +export type GapModifiers = Modifier; +export type ContainerModifiers = AlignmentModifiers & PositionModifiers & PaddingModifiers & MarginModifiers & FlexModifiers & BorderRadiusModifiers & BackgroundColorModifier & GapModifiers; +export declare function extractColorValue(props: Dictionary): any; +export declare function extractBackgroundColorValue(props: Dictionary): any; +export declare function extractTypographyValue(props: Dictionary): object | undefined; +export declare function extractPaddingValues(props: Dictionary): Partial>; +export declare function extractMarginValues(props: Dictionary): Partial>; +export declare function extractGapValues(props: Dictionary): number | undefined; +export declare function extractAlignmentsValues(props: Dictionary): any; +export declare function extractPositionStyle(props: Dictionary): {} | undefined; +export declare function extractFlexStyle(props: Dictionary): Partial> | undefined; +export declare function extractAccessibilityProps(props?: any): Partial; +export declare function extractAnimationProps(props?: any): Pick; +export declare function extractBorderRadiusValue(props: Dictionary): number | undefined; +export declare function extractModifierProps(props: Dictionary): _.Dictionary; +/** + * TODO: + * @deprecated switch to Modifiers#extractComponentProps + */ +export declare function extractOwnProps(props: Dictionary, ignoreProps: string[]): _.Omit<_.Dictionary, string>; +export declare function extractComponentProps(component: any, props: Dictionary, ignoreProps?: string[]): _.Omit<_.Dictionary, string>; +export declare function getThemeProps(props?: T, context?: any, componentDisplayName?: string): T; +export declare function generateModifiersStyle(options: ModifiersOptions | undefined, props: Dictionary): ExtractedStyle; +export declare function getAlteredModifiersOptions(currentProps: any, nextProps: any): AlteredOptions; +export {}; diff --git a/src/commons/modifiers.js b/src/commons/modifiers.js new file mode 100644 index 0000000000..c11e67569a --- /dev/null +++ b/src/commons/modifiers.js @@ -0,0 +1,384 @@ +import _isEqual from "lodash/isEqual"; +import _keys from "lodash/keys"; +import _union from "lodash/union"; +import _isFunction from "lodash/isFunction"; +import _omit from "lodash/omit"; +import _flow from "lodash/flow"; +import _find from "lodash/find"; +import _pick from "lodash/pick"; +import _pickBy from "lodash/pickBy"; +import _isEmpty from "lodash/isEmpty"; +import _split from "lodash/split"; +import _filter from "lodash/filter"; +import _includes from "lodash/includes"; +import _forEach from "lodash/forEach"; +import _findLast from "lodash/findLast"; +import { StyleSheet } from 'react-native'; +import { Typography, Colors, BorderRadiuses, Spacings, ThemeManager } from "../style"; +export const FLEX_KEY_PATTERN = /^flex(G|S)?(-\d*)?$/; +export const PADDING_KEY_PATTERN = new RegExp(`padding[LTRBHV]?-([0-9]*|${Spacings.getKeysPattern()})`); +export const MARGIN_KEY_PATTERN = new RegExp(`margin[LTRBHV]?-([0-9]*|${Spacings.getKeysPattern()})`); +export const ALIGNMENT_KEY_PATTERN = /(left|top|right|bottom|center|centerV|centerH|spread)/; +export const POSITION_KEY_PATTERN = /^abs([F|L|R|T|B|V|H])?$/; +const BACKGROUND_COLOR_KEYS_PATTERN = Colors.getBackgroundKeysPattern(); +export const GAP_KEY_PATTERN = new RegExp(`gap-([0-9]*|${Spacings.getKeysPattern()})`); +const PADDING_VARIATIONS = { + padding: 'padding', + paddingL: 'paddingLeft', + paddingT: 'paddingTop', + paddingR: 'paddingRight', + paddingB: 'paddingBottom', + paddingH: 'paddingHorizontal', + paddingV: 'paddingVertical' +}; +const MARGIN_VARIATIONS = { + margin: 'margin', + marginL: 'marginLeft', + marginT: 'marginTop', + marginR: 'marginRight', + marginB: 'marginBottom', + marginH: 'marginHorizontal', + marginV: 'marginVertical' +}; +const STYLE_KEY_CONVERTERS = { + flex: 'flex', + flexG: 'flexGrow', + flexS: 'flexShrink' +}; + +// TODO: migrate other modifiers to the same new structure as Margin modifier, using template literals + +// TODO: This caused issue with with some typings that inherit this type +// export type MarginModifiers = Partial<{[key: `${MarginLiterals}-${number}`]: boolean}>; + +export function extractColorValue(props) { + const colorPropsKeys = Object.keys(props).filter(key => Colors[key] !== undefined); + const colorKey = _findLast(colorPropsKeys, colorKey => props[colorKey] === true); + return Colors[colorKey]; +} +export function extractBackgroundColorValue(props) { + let backgroundColor; + const keys = Object.keys(props); + const bgProp = _findLast(keys, prop => BACKGROUND_COLOR_KEYS_PATTERN.test(prop) && !!props[prop]); + if (props[bgProp]) { + const key = bgProp.replace(BACKGROUND_COLOR_KEYS_PATTERN, ''); + backgroundColor = Colors[key]; + } + return backgroundColor; +} +export function extractTypographyValue(props) { + const typographyPropsKeys = Object.keys(props).filter(key => Typography[key] !== undefined); + let typography; + _forEach(typographyPropsKeys, key => { + if (props[key] === true) { + typography = { + ...typography, + ...Typography[key] + }; + } + }); + return typography; +} +export function extractPaddingValues(props) { + const paddings = {}; + const paddingPropsKeys = Object.keys(props).filter(key => PADDING_KEY_PATTERN.test(key)); + _forEach(paddingPropsKeys, key => { + if (props[key] === true) { + const [paddingKey, paddingValue] = key.split('-'); + const paddingVariation = PADDING_VARIATIONS[paddingKey]; + if (!isNaN(Number(paddingValue))) { + paddings[paddingVariation] = Number(paddingValue); + } else if (Spacings.getKeysPattern().test(paddingValue)) { + paddings[paddingVariation] = Spacings[paddingValue]; + } + } + }); + return paddings; +} +export function extractMarginValues(props) { + const margins = {}; + const marginPropsKeys = Object.keys(props).filter(key => MARGIN_KEY_PATTERN.test(key)); + _forEach(marginPropsKeys, key => { + if (props[key] === true) { + const [marginKey, marginValue] = key.split('-'); + const paddingVariation = MARGIN_VARIATIONS[marginKey]; + if (!isNaN(Number(marginValue))) { + margins[paddingVariation] = Number(marginValue); + } else if (Spacings.getKeysPattern().test(marginValue)) { + margins[paddingVariation] = Spacings[marginValue]; + } + } + }); + return margins; +} +export function extractGapValues(props) { + const gapPropsKeys = Object.keys(props).filter(key => GAP_KEY_PATTERN.test(key)); + // Taking only the last one + const gapModifier = _findLast(gapPropsKeys, key => props[key] === true); + if (gapModifier) { + const [, gapValue] = gapModifier.split('-'); + const parsedNumber = Number(gapValue); + if (!isNaN(parsedNumber)) { + return parsedNumber; + } else if (Spacings.getKeysPattern().test(gapValue)) { + return Spacings[gapValue]; + } + } +} +export function extractAlignmentsValues(props) { + const { + row, + center + } = props; + const alignments = {}; + const alignmentRules = {}; + if (row) { + alignments.flexDirection = 'row'; + alignmentRules.justifyContent = ['left', 'right', 'centerH', 'spread']; + alignmentRules.alignItems = ['top', 'bottom', 'centerV']; + } else { + alignmentRules.justifyContent = ['top', 'bottom', 'centerV', 'spread']; + alignmentRules.alignItems = ['left', 'right', 'centerH']; + } + _forEach(alignmentRules, (positions, attribute) => { + _forEach(positions, position => { + if (props[position]) { + if (_includes(['top', 'left'], position)) { + alignments[attribute] = 'flex-start'; + } else if (_includes(['bottom', 'right'], position)) { + alignments[attribute] = 'flex-end'; + } else if (_includes(['centerH', 'centerV'], position)) { + alignments[attribute] = 'center'; + } else if (position === 'spread') { + alignments[attribute] = 'space-between'; + } + } + }); + }); + if (center) { + alignments.justifyContent = 'center'; + alignments.alignItems = 'center'; + } + return alignments; +} +export function extractPositionStyle(props) { + const POSITION_CONVERSIONS = { + F: 'Fill', + T: 'Top', + B: 'Bottom', + L: 'Left', + R: 'Right', + H: 'Horizontal', + V: 'Vertical' + }; + const keys = Object.keys(props); + const positionProps = _filter(keys, prop => POSITION_KEY_PATTERN.test(prop) && !!props[prop]); + let style = {}; + _forEach(positionProps, positionProp => { + const positionVariationKey = _split(positionProp, 'abs')[1]; + if (positionVariationKey) { + const positionVariation = POSITION_CONVERSIONS[positionVariationKey]; + const styleKey = `absolute${positionVariation}`; + style = { + ...style, + ...styles[styleKey] + }; + } + style = { + ...style, + ...styles.absolute + }; + }); + return _isEmpty(style) ? undefined : style; +} +export function extractFlexStyle(props) { + const keys = Object.keys(props); + const flexProp = keys.find(item => FLEX_KEY_PATTERN.test(item)); + if (flexProp && props[flexProp] === true) { + const [flexKey, flexValue] = flexProp.split('-'); + const convertedFlexKey = STYLE_KEY_CONVERTERS[flexKey]; + const flexValueAsNumber = _isEmpty(flexValue) ? 1 : Number(flexValue); + return { + [convertedFlexKey]: flexValueAsNumber + }; + } +} + +//@ts-ignore +export function extractAccessibilityProps(props = this.props) { + return _pickBy(props, (_value, key) => { + return /.*ccessib.*/.test(key); + }); +} + +//@ts-ignore +export function extractAnimationProps(props = this.props) { + return _pick(props, ['animation', 'duration', 'delay', 'direction', 'easing', 'iterationCount', 'transition', 'onAnimationBegin', 'onAnimationEnd', 'useNativeDriver']); +} +export function extractBorderRadiusValue(props) { + let borderRadius; + const keys = Object.keys(props); + const radiusProp = keys.find(prop => BorderRadiuses.getKeysPattern().test(prop) && props[prop]); + if (radiusProp) { + borderRadius = BorderRadiuses[radiusProp]; + } + return borderRadius; +} +export function extractModifierProps(props) { + const patterns = [FLEX_KEY_PATTERN, PADDING_KEY_PATTERN, MARGIN_KEY_PATTERN, ALIGNMENT_KEY_PATTERN, GAP_KEY_PATTERN, Colors.getBackgroundKeysPattern()]; + const modifierProps = _pickBy(props, (_value, key) => { + const isModifier = _find(patterns, pattern => pattern.test(key)); + return !!isModifier; + }); + return modifierProps; +} + +/** + * TODO: + * @deprecated switch to Modifiers#extractComponentProps + */ +export function extractOwnProps(props, ignoreProps) { + //@ts-ignore + const ownPropTypes = this.propTypes; + const ownProps = _flow(props => _pickBy(props, (_value, key) => _includes(Object.keys(ownPropTypes), key)), props => _omit(props, ignoreProps))(props); + return ownProps; +} +export function extractComponentProps(component, props, ignoreProps = []) { + const componentPropTypes = component.propTypes; + const componentProps = _flow(props => _pickBy(props, (_value, key) => _includes(Object.keys(componentPropTypes), key)), props => _omit(props, ignoreProps))(props); + return componentProps; +} + +//@ts-ignore +export function getThemeProps(props = this.props, context = this.context, componentDisplayName = '') { + const componentName = + //@ts-ignore + componentDisplayName || this.displayName || this.constructor.displayName || this.constructor.name; + let themeProps; + if (_isFunction(ThemeManager.components[componentName])) { + themeProps = ThemeManager.components[componentName](props, context); + } else { + themeProps = ThemeManager.components[componentName]; + } + let forcedThemeProps; + if (_isFunction(ThemeManager.forcedThemeComponents[componentName])) { + forcedThemeProps = ThemeManager.forcedThemeComponents[componentName](props, context); + } else { + forcedThemeProps = ThemeManager.forcedThemeComponents[componentName]; + } + return { + ...themeProps, + ...props, + ...forcedThemeProps + }; +} +export function generateModifiersStyle(options = { + color: true, + typography: true, + backgroundColor: true, + borderRadius: true, + paddings: true, + margins: true, + alignments: true, + flex: true, + position: true, + gap: false +}, props) { + //@ts-ignore + const boundProps = props || this.props; + const style = {}; + if (!_find(boundProps, prop => prop === true)) { + return style; + } + if (options.color) { + style.color = extractColorValue(boundProps); + } + if (options.typography) { + style.typography = extractTypographyValue(boundProps); + } + if (options.backgroundColor) { + style.backgroundColor = extractBackgroundColorValue(boundProps); + } + if (options.borderRadius) { + style.borderRadius = extractBorderRadiusValue(boundProps); + } + if (options.paddings) { + style.paddings = extractPaddingValues(boundProps); + } + if (options.margins) { + style.margins = extractMarginValues(boundProps); + } + if (options.alignments) { + style.alignments = extractAlignmentsValues(boundProps); + } + if (options.flex) { + style.flexStyle = extractFlexStyle(boundProps); + } + if (options.position) { + style.positionStyle = extractPositionStyle(boundProps); + } + if (options.gap) { + style.gap = extractGapValues(boundProps); + } + return style; + // clean empty objects and undefined + // (!) This change is currently breaking UI layout for some reason - worth investigating + // return _.omitBy(style, value => _.isUndefined(value) || (_.isPlainObject(value) && _.isEmpty(value))); +} +export function getAlteredModifiersOptions(currentProps, nextProps) { + const ignoredKeys = ['children', 'forwardedRef', 'style', 'testID']; + const allKeys = _union([..._keys(currentProps), ..._keys(nextProps)]).filter(key => !ignoredKeys.includes(key)); + const changedKeys = _filter(allKeys, key => !_isEqual(currentProps[key], nextProps[key])); + const options = {}; + if (_find(changedKeys, key => FLEX_KEY_PATTERN.test(key))) { + options.flex = true; + } + if (_find(changedKeys, key => PADDING_KEY_PATTERN.test(key))) { + options.paddings = true; + } + if (_find(changedKeys, key => MARGIN_KEY_PATTERN.test(key))) { + options.margins = true; + } + if (_find(changedKeys, key => ALIGNMENT_KEY_PATTERN.test(key))) { + options.alignments = true; + } + if (_find(changedKeys, key => Colors.getBackgroundKeysPattern().test(key))) { + options.backgroundColor = true; + } + if (_find(changedKeys, key => POSITION_KEY_PATTERN.test(key))) { + options.position = true; + } + return options; +} +const styles = StyleSheet.create({ + absolute: { + position: 'absolute' + }, + absoluteFill: StyleSheet.absoluteFillObject, + absoluteTop: { + position: 'absolute', + top: 0 + }, + absoluteBottom: { + position: 'absolute', + bottom: 0 + }, + absoluteLeft: { + position: 'absolute', + left: 0 + }, + absoluteRight: { + position: 'absolute', + right: 0 + }, + absoluteVertical: { + position: 'absolute', + top: 0, + bottom: 0 + }, + absoluteHorizontal: { + position: 'absolute', + left: 0, + right: 0 + } +}); \ No newline at end of file diff --git a/src/commons/new/index.d.ts b/src/commons/new/index.d.ts new file mode 100644 index 0000000000..b77ef4b584 --- /dev/null +++ b/src/commons/new/index.d.ts @@ -0,0 +1,8 @@ +export { default as UIComponent } from '../UIComponent'; +export { default as asBaseComponent, BaseComponentInjectedProps } from '../asBaseComponent'; +export { default as forwardRef, ForwardRefInjectedProps } from '../forwardRef'; +export { default as withScrollEnabler, WithScrollEnablerProps } from '../withScrollEnabler'; +export { default as withScrollReached, WithScrollReachedProps } from '../withScrollReached'; +export { default as Constants } from '../Constants'; +export { default as Config } from '../Config'; +export { ContainerModifiers, AlignmentModifiers, MarginModifiers, PaddingModifiers, TypographyModifiers, ColorsModifiers, BackgroundColorModifier, FlexModifiers } from '../modifiers'; diff --git a/src/commons/new/index.js b/src/commons/new/index.js new file mode 100644 index 0000000000..d517f2af8f --- /dev/null +++ b/src/commons/new/index.js @@ -0,0 +1,9 @@ +// TODO: this file should replace commons/index.js +export { default as UIComponent } from "../UIComponent"; +export { default as asBaseComponent, BaseComponentInjectedProps } from "../asBaseComponent"; +export { default as forwardRef, ForwardRefInjectedProps } from "../forwardRef"; +export { default as withScrollEnabler, WithScrollEnablerProps } from "../withScrollEnabler"; +export { default as withScrollReached, WithScrollReachedProps } from "../withScrollReached"; +export { default as Constants } from "../Constants"; +export { default as Config } from "../Config"; +export { ContainerModifiers, AlignmentModifiers, MarginModifiers, PaddingModifiers, TypographyModifiers, ColorsModifiers, BackgroundColorModifier, FlexModifiers } from "../modifiers"; \ No newline at end of file diff --git a/src/commons/new.ts b/src/commons/new/index.ts similarity index 55% rename from src/commons/new.ts rename to src/commons/new/index.ts index c1da1691b4..beb2b76eeb 100644 --- a/src/commons/new.ts +++ b/src/commons/new/index.ts @@ -1,11 +1,11 @@ // TODO: this file should replace commons/index.js -export {default as UIComponent} from './UIComponent'; -export {default as asBaseComponent, BaseComponentInjectedProps} from './asBaseComponent'; -export {default as forwardRef, ForwardRefInjectedProps} from './forwardRef'; -export {default as withScrollEnabler, WithScrollEnablerProps} from './withScrollEnabler'; -export {default as withScrollReached, WithScrollReachedProps} from './withScrollReached'; -export {default as Constants} from './Constants'; -export {default as Config} from './Config'; +export {default as UIComponent} from '../UIComponent'; +export {default as asBaseComponent, BaseComponentInjectedProps} from '../asBaseComponent'; +export {default as forwardRef, ForwardRefInjectedProps} from '../forwardRef'; +export {default as withScrollEnabler, WithScrollEnablerProps} from '../withScrollEnabler'; +export {default as withScrollReached, WithScrollReachedProps} from '../withScrollReached'; +export {default as Constants} from '../Constants'; +export {default as Config} from '../Config'; export { ContainerModifiers, @@ -16,4 +16,4 @@ export { ColorsModifiers, BackgroundColorModifier, FlexModifiers -} from './modifiers'; +} from '../modifiers'; diff --git a/src/commons/withScrollEnabler.d.ts b/src/commons/withScrollEnabler.d.ts new file mode 100644 index 0000000000..0241df6d37 --- /dev/null +++ b/src/commons/withScrollEnabler.d.ts @@ -0,0 +1,13 @@ +import React from 'react'; +import { LayoutChangeEvent } from 'react-native'; +type ScrollEnablerProps = { + onContentSizeChange: (contentWidth: number, contentHeight: number) => void; + onLayout: (event: LayoutChangeEvent) => void; + scrollEnabled: boolean; +}; +export type WithScrollEnablerProps = { + scrollEnablerProps: ScrollEnablerProps; + ref?: any; +}; +declare function withScrollEnabler(WrappedComponent: React.ComponentType): React.ComponentType & STATICS; +export default withScrollEnabler; diff --git a/src/commons/withScrollEnabler.js b/src/commons/withScrollEnabler.js new file mode 100644 index 0000000000..cb91f6f5d9 --- /dev/null +++ b/src/commons/withScrollEnabler.js @@ -0,0 +1,55 @@ +import React, { useState, useCallback, useRef } from 'react'; +import forwardRef from "./forwardRef"; +import hoistStatics from 'hoist-non-react-statics'; +function withScrollEnabler(WrappedComponent) { + const ScrollEnabler = props => { + const [scrollEnabled, setScrollEnabled] = useState(true); + const contentSize = useRef(0); + const layoutSize = useRef(0); + const checkScroll = useCallback(() => { + const isScrollEnabled = Math.floor(contentSize.current) > layoutSize.current; + if (isScrollEnabled !== scrollEnabled) { + setScrollEnabled(isScrollEnabled); + } + }, [scrollEnabled]); + const onContentSizeChange = useCallback((contentWidth, contentHeight) => { + const size = props.horizontal ? contentWidth : contentHeight; + if (size !== contentSize.current) { + contentSize.current = size; + if (layoutSize.current > 0) { + checkScroll(); + } + } + }, [props.horizontal, checkScroll]); + const onLayout = useCallback(event => { + const { + nativeEvent: { + layout: { + width, + height + } + } + } = event; + const size = props.horizontal ? width : height; + if (size !== layoutSize.current) { + layoutSize.current = size; + if (contentSize.current > 0) { + checkScroll(); + } + } + }, [props.horizontal, checkScroll]); + return ; + }; + hoistStatics(ScrollEnabler, WrappedComponent); + ScrollEnabler.displayName = WrappedComponent.displayName; + //@ts-ignore + ScrollEnabler.propTypes = WrappedComponent.propTypes; + //@ts-ignore + ScrollEnabler.defaultProps = WrappedComponent.defaultProps; + return forwardRef(ScrollEnabler); +} +export default withScrollEnabler; \ No newline at end of file diff --git a/src/commons/withScrollReached.d.ts b/src/commons/withScrollReached.d.ts new file mode 100644 index 0000000000..7dcd6d9505 --- /dev/null +++ b/src/commons/withScrollReached.d.ts @@ -0,0 +1,35 @@ +import React from 'react'; +import { NativeSyntheticEvent, NativeScrollEvent } from 'react-native'; +type ScrollReachedProps = { + onScroll: (event: NativeSyntheticEvent) => void; + /** + * Is the scroll at the start (or equal\smaller than the threshold if one was given) + */ + isScrollAtStart?: boolean; + /** + * Is the scroll at the end (or equal\greater than the threshold if one was given) + */ + isScrollAtEnd?: boolean; +}; +export type WithScrollReachedOptionsProps = { + /** + * Whether the scroll is horizontal. + */ + horizontal?: boolean; + /** + * Allows to be notified prior to actually reaching the start \ end of the scroll (by the threshold). + * Should be a positive value. + */ + threshold?: number; +}; +export type WithScrollReachedProps = { + scrollReachedProps: ScrollReachedProps; + ref?: any; +}; +/** + * @description: Add scroll reached which notifies on reaching start \ end of ScrollView \ FlatList + * @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/WithScrollReachedScreen.tsx + * @notes: Send `props.scrollReachedProps.onScroll` to your onScroll and receive via props.scrollReachedProps.isScrollAtStart props.scrollReachedProps.isScrollAtEnd + */ +declare function withScrollReached(WrappedComponent: React.ComponentType, options?: WithScrollReachedOptionsProps): React.ComponentType & STATICS; +export default withScrollReached; diff --git a/src/commons/withScrollReached.js b/src/commons/withScrollReached.js new file mode 100644 index 0000000000..ed8fbcb531 --- /dev/null +++ b/src/commons/withScrollReached.js @@ -0,0 +1,66 @@ +import React, { useState, useCallback } from 'react'; +import forwardRef from "./forwardRef"; +import hoistStatics from 'hoist-non-react-statics'; +import Constants from "./Constants"; +const DEFAULT_THRESHOLD = Constants.isAndroid ? 1 : 0; + +/** + * @description: Add scroll reached which notifies on reaching start \ end of ScrollView \ FlatList + * @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/WithScrollReachedScreen.tsx + * @notes: Send `props.scrollReachedProps.onScroll` to your onScroll and receive via props.scrollReachedProps.isScrollAtStart props.scrollReachedProps.isScrollAtEnd + */ +function withScrollReached(WrappedComponent, options = {}) { + const ScrollReachedDetector = props => { + // The scroll starts at the start, from what I've tested this works fine + const [isScrollAtStart, setScrollAtStart] = useState(true); + const [isScrollAtEnd, setScrollAtEnd] = useState(false); + const onScroll = useCallback(event => { + const { + nativeEvent: { + layoutMeasurement: { + width: layoutWidth, + height: layoutHeight + }, + contentOffset: { + x: offsetX, + y: offsetY + }, + contentSize: { + width: contentWidth, + height: contentHeight + } + } + } = event; + const horizontal = options.horizontal; + const threshold = options.threshold || DEFAULT_THRESHOLD; + const layoutSize = horizontal ? layoutWidth : layoutHeight; + let offset = horizontal ? offsetX : offsetY; + const contentSize = horizontal ? contentWidth : contentHeight; + if (horizontal && Constants.isRTL && Constants.isAndroid) { + const scrollingWidth = Math.max(0, contentSize - layoutSize); + offset = scrollingWidth - offset; + } + const closeToStart = offset <= threshold; + if (closeToStart !== isScrollAtStart) { + setScrollAtStart(closeToStart); + } + const closeToEnd = layoutSize + offset >= contentSize - threshold; + if (closeToEnd !== isScrollAtEnd) { + setScrollAtEnd(closeToEnd); + } + }, [isScrollAtStart, isScrollAtEnd]); + return ; + }; + hoistStatics(ScrollReachedDetector, WrappedComponent); + ScrollReachedDetector.displayName = WrappedComponent.displayName; + //@ts-ignore + ScrollReachedDetector.propTypes = WrappedComponent.propTypes; + //@ts-ignore + ScrollReachedDetector.defaultProps = WrappedComponent.defaultProps; + return forwardRef(ScrollReachedDetector); +} +export default withScrollReached; \ No newline at end of file diff --git a/src/components/WheelPicker/Item.d.ts b/src/components/WheelPicker/Item.d.ts new file mode 100644 index 0000000000..029f9096fe --- /dev/null +++ b/src/components/WheelPicker/Item.d.ts @@ -0,0 +1,28 @@ +import React from 'react'; +import { TextStyle } from 'react-native'; +import Animated from 'react-native-reanimated'; +import { TextProps } from '../text'; +import { WheelPickerAlign, WheelPickerItemValue } from './types'; +export interface WheelPickerItemProps { + label: string; + value: T; + align?: WheelPickerAlign; + disableRTL?: boolean; +} +interface InternalProps extends WheelPickerItemProps { + index: number; + offset: Animated.SharedValue; + itemHeight: number; + activeColor?: string; + inactiveColor?: string; + style?: TextStyle; + onSelect: (index: number) => void; + onPress?: () => void; + centerH?: boolean; + fakeLabel?: string; + fakeLabelStyle?: TextStyle; + fakeLabelProps?: TextProps; + testID?: string; +} +declare const _default: React.MemoExoticComponent<((props: InternalProps) => React.JSX.Element)>; +export default _default; diff --git a/src/components/WheelPicker/Item.js b/src/components/WheelPicker/Item.js new file mode 100644 index 0000000000..b2bb684120 --- /dev/null +++ b/src/components/WheelPicker/Item.js @@ -0,0 +1,86 @@ +import React, { useCallback, useMemo, memo, useRef } from 'react'; +import { StyleSheet } from 'react-native'; +import Animated, { interpolateColor, useAnimatedStyle } from 'react-native-reanimated'; +import { Colors, Spacings } from "../../style"; +import { useThemeProps } from "../../hooks"; +import Text from "../text"; +import TouchableOpacity from "../touchableOpacity"; +import { WheelPickerAlign } from "./types"; +const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity); +const AnimatedText = Animated.createAnimatedComponent(Text); +const WheelPickerItem = props => { + const themeProps = useThemeProps(props, 'WheelPickerItem'); + const { + index, + label, + fakeLabel, + fakeLabelStyle, + fakeLabelProps, + itemHeight, + onSelect, + onPress, + offset, + activeColor = Colors.$textPrimary, + inactiveColor = Colors.$textNeutralHeavy, + style, + testID, + centerH = true, + align, + disableRTL + } = themeProps; + const selectItem = useCallback(() => onSelect(index), [index]); + const itemOffset = index * itemHeight; + const _activeColor = useRef(activeColor.toString()); + const _inactiveColor = useRef(inactiveColor.toString()); + const animatedColorStyle = useAnimatedStyle(() => { + const color = interpolateColor(offset.value, [itemOffset - itemHeight, itemOffset, itemOffset + itemHeight], [_inactiveColor.current, _activeColor.current, _inactiveColor.current]); + return { + color + }; + }, [itemHeight]); + const containerStyle = useMemo(() => { + return [{ + height: itemHeight + }, styles.container, disableRTL && styles.disableRTL]; + }, [itemHeight, disableRTL]); + const textWithLabelPaddingStyle = useMemo(() => { + return disableRTL ? { + marginRight: Spacings.s5 + } : { + marginLeft: Spacings.s5 + }; + }, [disableRTL]); + const textStyle = useMemo(() => { + return [animatedColorStyle, style, fakeLabel ? textWithLabelPaddingStyle : styles.textPadding]; + }, [style, fakeLabel, animatedColorStyle, textWithLabelPaddingStyle]); + const _onPress = useCallback(() => { + selectItem(); + onPress?.(); + }, [onPress, selectItem]); + const _fakeLabelStyle = useMemo(() => StyleSheet.flatten([fakeLabelStyle, styles.hidden]), [fakeLabelStyle]); + return + + {label} + + {fakeLabel && + {fakeLabel} + } + ; +}; +export default memo(WheelPickerItem); +const styles = StyleSheet.create({ + container: { + minWidth: Spacings.s10 + }, + textPadding: { + paddingHorizontal: Spacings.s5 + }, + disableRTL: { + flexDirection: 'row-reverse' + }, + hidden: { + opacity: 0 + } +}); \ No newline at end of file diff --git a/src/components/WheelPicker/WheelPicker.driver.d.ts b/src/components/WheelPicker/WheelPicker.driver.d.ts new file mode 100644 index 0000000000..358c971e1c --- /dev/null +++ b/src/components/WheelPicker/WheelPicker.driver.d.ts @@ -0,0 +1,18 @@ +import { ComponentProps } from '../../testkit/new/Component.driver'; +export declare const WheelPickerDriver: (props: ComponentProps) => { + getListHeight: () => any; + moveToItem: (index: number, itemHeight?: number, numberOfRows?: number) => void; + getLabel: () => string | (string | import("react-test-renderer").ReactTestInstance)[]; + scroll: (contentOffset: Partial, options?: { + contentInset: import("react-native/types").NativeScrollRectangle; + zoomScale: number; + layoutMeasurement: import("react-native/types").NativeScrollSize; + contentSize: import("react-native/types").NativeScrollSize; + velocity?: import("react-native/types").NativeScrollVelocity | undefined; + targetContentOffset?: import("react-native/types").NativeScrollPoint | undefined; + } | undefined) => void; + triggerEvent: (eventName?: string | undefined, event?: Partial | undefined) => void; + getElement: () => import("react-test-renderer").ReactTestInstance; + queryElement: () => import("react-test-renderer").ReactTestInstance | undefined; + exists: () => boolean; +}; diff --git a/src/components/WheelPicker/WheelPicker.driver.js b/src/components/WheelPicker/WheelPicker.driver.js new file mode 100644 index 0000000000..1a921df068 --- /dev/null +++ b/src/components/WheelPicker/WheelPicker.driver.js @@ -0,0 +1,46 @@ +import { ITEM_HEIGHT } from "./index"; +import { useComponentDriver } from "../../testkit/new/Component.driver"; +import { useScrollableDriver } from "../../testkit/new/useScrollable.driver"; +import { TextDriver } from "../../components/text/Text.driver.new"; +export const WheelPickerDriver = props => { + const driver = useComponentDriver(props); + const listDriver = useScrollableDriver(useComponentDriver({ + renderTree: props.renderTree, + testID: `${props.testID}.list` + })); + const itemsLength = listDriver.getElement().props.data?.length ?? 0; + const moveToItem = (index, itemHeight = ITEM_HEIGHT, numberOfRows = itemsLength) => { + listDriver.triggerEvent('onScrollBeginDrag'); + listDriver.triggerEvent('onMomentumScrollEnd', { + contentOffset: { + x: 0, + y: itemHeight * index + }, + contentSize: { + height: numberOfRows * itemHeight, + width: 400 + }, + layoutMeasurement: { + height: 100, + width: 400 + } + }); + }; + const getListHeight = () => { + return listDriver.getElement().props.height; + }; + const labelDriver = TextDriver({ + renderTree: props.renderTree, + testID: `${props.testID}.label` + }); + const getLabel = () => { + return labelDriver.getText(); + }; + return { + ...driver, + ...listDriver, + getListHeight, + moveToItem, + getLabel + }; +}; \ No newline at end of file diff --git a/src/components/WheelPicker/WheelPickerItem.driver.d.ts b/src/components/WheelPicker/WheelPickerItem.driver.d.ts new file mode 100644 index 0000000000..3a90e945c5 --- /dev/null +++ b/src/components/WheelPicker/WheelPickerItem.driver.d.ts @@ -0,0 +1,8 @@ +import { ComponentProps } from '../../testkit/new/Component.driver'; +export declare const WheelPickerItemDriver: (props: ComponentProps) => { + getLabel: () => string | (string | import("react-test-renderer").ReactTestInstance)[]; + getLabelStyle: () => import("react-native/types").TextStyle; + getElement: () => import("react-test-renderer").ReactTestInstance; + queryElement: () => import("react-test-renderer").ReactTestInstance | undefined; + exists: () => boolean; +}; diff --git a/src/components/WheelPicker/WheelPickerItem.driver.js b/src/components/WheelPicker/WheelPickerItem.driver.js new file mode 100644 index 0000000000..c3677ed936 --- /dev/null +++ b/src/components/WheelPicker/WheelPickerItem.driver.js @@ -0,0 +1,25 @@ +import { useComponentDriver } from "../../testkit/new/Component.driver"; +// import {usePressableDriver} from '../../testkit'; +import { TextDriver } from "../../components/text/Text.driver.new"; +// import {WheelPickerItemProps} from './index'; + +export const WheelPickerItemDriver = props => { + const driver = useComponentDriver(props); + // const driver = usePressableDriver(useComponentDriver(props)); + + const labelDriver = TextDriver({ + renderTree: props.renderTree, + testID: `${props.testID}.text` + }); + const getLabel = () => { + return labelDriver.getText(); + }; + const getLabelStyle = () => { + return labelDriver.getStyle(); // NOTE: when there's active/inactive colors the color will be animated sharedValue instead of string + }; + return { + ...driver, + getLabel, + getLabelStyle + }; +}; \ No newline at end of file diff --git a/src/components/WheelPicker/__tests__/index.spec.js b/src/components/WheelPicker/__tests__/index.spec.js new file mode 100644 index 0000000000..8b40f344a4 --- /dev/null +++ b/src/components/WheelPicker/__tests__/index.spec.js @@ -0,0 +1,121 @@ +import _times from "lodash/times"; +import React from 'react'; +import { render /* , act, waitFor */ } from '@testing-library/react-native'; +import { Colors } from "../../../style"; +import WheelPicker from "../index"; +import { WheelPickerDriver } from "../WheelPicker.driver"; +import { WheelPickerItemDriver } from "../WheelPickerItem.driver"; +const ITEM_HEIGHT = 50; +const NUM_OF_ROWS = 10; +const testID = 'wheel'; +const onChange = jest.fn(); +const TestCase = props => { + return i).map(item => ({ + label: `item #${item}`, + value: item, + testID: `${item}` + }))} initialValue={0} onChange={onChange} numberOfVisibleRows={NUM_OF_ROWS} itemHeight={ITEM_HEIGHT} activeTextColor={Colors.red30} inactiveTextColor={Colors.blue30} {...props} />; +}; +describe('WheelPicker', () => { + beforeEach(() => { + onChange.mockClear(); + }); + describe('FlatList', () => { + it('should present $NUM_OF_ROWS rows', () => { + const renderTree = render(); + const driver = WheelPickerDriver({ + renderTree, + testID + }); + expect(driver.getListHeight()).toBe(NUM_OF_ROWS * ITEM_HEIGHT); + }); + it('should call onChange after scrolling ends with default itemHeight and numberOfRows', () => { + const props = { + itemHeight: undefined, + numberOfVisibleRows: undefined + }; + const renderTree = render(); + const driver = WheelPickerDriver({ + renderTree, + testID + }); + driver.moveToItem(4); + expect(onChange).toHaveBeenCalledWith(4, 4); + driver.moveToItem(7); + expect(onChange).toHaveBeenCalledWith(7, 7); + }); + it('should call onChange after scrolling ends', () => { + const renderTree = render(); + const driver = WheelPickerDriver({ + renderTree, + testID + }); + driver.moveToItem(4, ITEM_HEIGHT); + expect(onChange).toHaveBeenCalledWith(4, 4); + driver.moveToItem(7, ITEM_HEIGHT); + expect(onChange).toHaveBeenCalledWith(7, 7); + }); + }); + describe('initialValue', () => { + it('should not call onChange when initialValue is updated', () => { + const renderTree = render(); + renderTree.rerender(); + expect(onChange).not.toHaveBeenCalled(); + }); + }); + describe('label', () => { + it('should return label', () => { + const label = 'Hours'; + const renderTree = render(); + const driver = WheelPickerDriver({ + renderTree, + testID + }); + expect(driver.getLabel()).toEqual(label); + }); + }); + describe('PickerItem', () => { + it('should get first item\'s label', () => { + const renderTree = render(); + const index = 0; + const driver = WheelPickerItemDriver({ + renderTree, + testID: `${index}` + }); + expect(driver.getLabel()).toEqual('item #0'); + }); + it('should get first item\'s text style when no active/inactive colors', () => { + const renderTree = render(); + const index = 0; + const driver = WheelPickerItemDriver({ + renderTree, + testID: `${index}` + }); + expect(driver.getLabelStyle()?.color).toEqual(Colors.green30); + }); + + //TODO: Fix these test's using AnimatedStyle mocking + // it('should call onChange after second item is pressed', async () => { + // const renderTree = render(); + // const index = 1; + // const driver = WheelPickerItemDriver({renderTree, testID: `${index}`}); + + // driver.press(); + + // expect(await onChange).toHaveBeenCalledTimes(1); + // expect(onChange).toHaveBeenCalledWith(1); + // }); + + // it('should not call onChange after first item is pressed', async () => { + // const renderTree = render(); + // const index = 0; + // const driver = WheelPickerItemDriver({renderTree, testID: `${index}`}); + + // driver.press(); + + // expect(onChange).not.toHaveBeenCalledTimes(1); + // }); + }); +}); \ No newline at end of file diff --git a/src/components/WheelPicker/helpers/useListMiddleIndex.d.ts b/src/components/WheelPicker/helpers/useListMiddleIndex.d.ts new file mode 100644 index 0000000000..492d1958b3 --- /dev/null +++ b/src/components/WheelPicker/helpers/useListMiddleIndex.d.ts @@ -0,0 +1,6 @@ +type ItemType = { + itemHeight: number; + listSize: number; +}; +declare const _default: ({ itemHeight, listSize }: ItemType) => (offset: number) => number; +export default _default; diff --git a/src/components/WheelPicker/helpers/useListMiddleIndex.js b/src/components/WheelPicker/helpers/useListMiddleIndex.js new file mode 100644 index 0000000000..d0b34e702c --- /dev/null +++ b/src/components/WheelPicker/helpers/useListMiddleIndex.js @@ -0,0 +1,19 @@ +export default ({ + itemHeight, + listSize +}) => { + const valueInRange = (value, min, max) => { + if (value < min || value === -0) { + return min; + } + if (value > max) { + return max; + } + return value; + }; + const middleIndex = offset => { + const calculatedIndex = Math.round(offset / itemHeight); + return valueInRange(calculatedIndex, 0, listSize - 1); + }; + return middleIndex; +}; \ No newline at end of file diff --git a/src/components/WheelPicker/index.d.ts b/src/components/WheelPicker/index.d.ts new file mode 100644 index 0000000000..b357eb8639 --- /dev/null +++ b/src/components/WheelPicker/index.d.ts @@ -0,0 +1,88 @@ +import React from 'react'; +import { TextStyle, ViewStyle, FlatListProps } from 'react-native'; +import { TextProps } from '../text'; +import { FaderProps } from '../fader'; +import { WheelPickerItemProps } from './Item'; +import { WheelPickerAlign, WheelPickerItemValue } from './types'; +export { WheelPickerAlign, WheelPickerItemValue }; +export declare const ITEM_HEIGHT = 44; +export type WheelPickerProps = { + /** + * Initial value + */ + initialValue?: T; + /** + * Data source for WheelPicker + */ + items?: WheelPickerItemProps[]; + /** + * Describe the height of each item in the WheelPicker + * default value: 44 + */ + itemHeight?: number; + /** + * Describe the number of rows visible + * default value: 5 + */ + numberOfVisibleRows?: number; + /** + * Text color for the focused row + */ + activeTextColor?: string; + /** + * Text color for other, non-focused rows + */ + inactiveTextColor?: string; + /** + * Row text style + */ + textStyle?: Omit; + /** + * Additional label on the right of the item text + */ + label?: string; + /** + * The Additional label's style + */ + labelStyle?: TextStyle; + /** + * The Additional label's props + */ + labelProps?: TextProps; + /** + * Event, on active row change + */ + onChange?: (item: T, index: number) => void; + /** + * Container's ViewStyle, height is computed according to itemHeight * numberOfVisibleRows + */ + style?: Omit; + /** + * Support passing items as children props + */ + children?: JSX.Element | JSX.Element[]; + /** + * Align the content to center, right ot left (default: center) + */ + align?: WheelPickerAlign; + disableRTL?: boolean; + /** + * Extra style for the separators + */ + separatorsStyle?: ViewStyle; + testID?: string; + /** + * Change the default (white) tint color of the fade view. + */ + faderProps?: Omit; + /** + * Props to be sent to the FlatList + */ + flatListProps?: Partial>>; +}; +declare const WheelPicker: { + (props: WheelPickerProps): React.JSX.Element; + alignments: typeof WheelPickerAlign; +}; +export default WheelPicker; +export { WheelPickerItemProps }; diff --git a/src/components/WheelPicker/index.js b/src/components/WheelPicker/index.js new file mode 100644 index 0000000000..718a3a920d --- /dev/null +++ b/src/components/WheelPicker/index.js @@ -0,0 +1,247 @@ +import _isUndefined from "lodash/isUndefined"; +import _isFunction from "lodash/isFunction"; // TODO: Support style customization +import React, { useCallback, useRef, useMemo, useEffect, useState } from 'react'; +import { StyleSheet } from 'react-native'; +import Animated, { useSharedValue, useAnimatedScrollHandler } from 'react-native-reanimated'; +import { FlatList, GestureHandlerRootView } from 'react-native-gesture-handler'; +import { Colors, Spacings } from "../../style"; +import { Constants } from "../../commons/new"; +import { useThemeProps } from "../../hooks"; +import View from "../view"; +import Text from "../text"; +import Fader, { FaderPosition } from "../fader"; +import Item, { WheelPickerItemProps } from "./Item"; +import usePresenter from "./usePresenter"; +import { WheelPickerAlign, WheelPickerItemValue } from "./types"; +export { WheelPickerAlign, WheelPickerItemValue }; +export const ITEM_HEIGHT = 44; +const WheelPicker = props => { + const AnimatedFlatList = useMemo(() => Animated.createAnimatedComponent(FlatList), []); + const themeProps = useThemeProps(props, 'WheelPicker'); + const { + items: propItems, + itemHeight = ITEM_HEIGHT, + numberOfVisibleRows = 5, + activeTextColor = Colors.$textPrimary, + inactiveTextColor, + textStyle, + label, + labelStyle, + labelProps, + onChange, + align = WheelPickerAlign.CENTER, + disableRTL, + style, + children, + initialValue, + separatorsStyle, + testID, + faderProps, + flatListProps + } = themeProps; + const scrollView = useRef(); + const offset = useSharedValue(0); + const scrollHandler = useAnimatedScrollHandler(e => { + offset.value = e.contentOffset.y; + }); + const shouldDisableRTL = useMemo(() => { + return Constants.isRTL && disableRTL; + }, [disableRTL]); + const { + height, + items, + index: currentIndex = 0, + getRowItemAtOffset + } = usePresenter({ + initialValue, + items: propItems, + children, + itemHeight, + preferredNumVisibleRows: numberOfVisibleRows + }); + const shouldSkipNextOnChange = useRef(false); + const prevIndex = useRef(currentIndex); + const [flatListWidth, setFlatListWidth] = useState(0); + const keyExtractor = useCallback((item, index) => `${item}.${index}`, []); + const androidFlatListProps = useMemo(() => { + if (Constants.isAndroid) { + return { + maxToRenderPerBatch: items.length + }; + } + }, [items]); + useEffect(() => { + // This effect should replace the onLayout function in the FlatList, should happen only once + scrollToIndex(currentIndex, true); + }, []); + useEffect(() => { + // This effect making sure to reset index if initialValue has changed + if (!_isUndefined(initialValue)) { + shouldSkipNextOnChange.current = true; + scrollToIndex(currentIndex, true); + } + }, [currentIndex]); + const _onChange = useCallback((value, index) => { + if (!shouldSkipNextOnChange.current) { + onChange?.(value, index); + } + }, [onChange]); + const disableOnChangeSkip = useCallback(() => { + shouldSkipNextOnChange.current = false; + }, []); + const onValueChange = useCallback(event => { + const { + value, + index + } = getRowItemAtOffset(event.nativeEvent.contentOffset.y); + _onChange(value, index); + }, [_onChange, getRowItemAtOffset]); + const onMomentumScrollEndAndroid = index => { + // handle Android bug: ScrollView does not call 'onMomentumScrollEnd' when scrolled programmatically (https://github.com/facebook/react-native/issues/26661) + if (Constants.isAndroid && prevIndex.current !== index) { + prevIndex.current = index; + _onChange(items?.[index]?.value, index); + } + }; + const scrollToOffset = (index, animated) => { + // TODO: we should remove this split (the getNode section) in V6 and remove support for reanimated 1 + //@ts-expect-error for some reason scrollToOffset isn't recognized + if (_isFunction(scrollView.current?.scrollToOffset)) { + //@ts-expect-error + scrollView.current?.scrollToOffset({ + offset: index * itemHeight, + animated + }); + } else { + //@ts-expect-error + scrollView.current?.getNode()?.scrollToOffset({ + offset: index * itemHeight, + animated + }); + } + }; + const scrollToIndex = (index, animated) => { + onMomentumScrollEndAndroid(index); + setTimeout(() => scrollToOffset(index, animated), 100); + }; + const selectItem = useCallback(index => { + shouldSkipNextOnChange.current = false; + scrollToIndex(index, true); + }, [itemHeight]); + const labelMargins = useMemo(() => { + return { + 'marginL-s2': !shouldDisableRTL, + 'marginR-s5': !shouldDisableRTL, + 'marginR-s2': !!shouldDisableRTL, + 'marginL-s5': !!shouldDisableRTL + }; + }, [shouldDisableRTL]); + const fakeLabelProps = useMemo(() => { + return { + ...labelMargins, + ...labelProps + }; + }, [labelMargins, labelProps]); + const renderItem = useCallback(({ + item, + index + }) => { + return ; + }, [itemHeight, shouldDisableRTL, fakeLabelProps, offset, testID, labelStyle, label, activeTextColor, inactiveTextColor, textStyle, selectItem]); + const getItemLayout = useCallback((_data, index) => { + return { + length: itemHeight, + offset: itemHeight * index, + index + }; + }, [itemHeight]); + const updateFlatListWidth = useCallback(width => { + setFlatListWidth(width); + }, []); + const alignmentStyle = useMemo(() => { + return align === WheelPickerAlign.RIGHT ? { + alignSelf: 'flex-end' + } : align === WheelPickerAlign.LEFT ? { + alignSelf: 'flex-start' + } : { + alignSelf: 'center' + }; + }, [align]); + const contentContainerStyle = useMemo(() => { + return [{ + paddingVertical: height / 2 - itemHeight / 2 + }, alignmentStyle]; + }, [height, itemHeight, alignmentStyle]); + const labelContainerStyle = useMemo(() => { + return [{ + position: 'absolute', + top: 0, + bottom: 0 + }, alignmentStyle]; + }, [alignmentStyle]); + const labelInnerContainerStyle = useMemo(() => { + return [styles.label, shouldDisableRTL ? { + left: 0 + } : { + right: 0 + }]; + }, [shouldDisableRTL]); + const labelContainer = useMemo(() => { + return ( + // @ts-expect-error + + + + {label} + + + + ); + }, [labelMargins, flatListWidth, labelContainerStyle, labelInnerContainerStyle, label, labelProps, activeTextColor, labelStyle, testID]); + const fader = useMemo(() => position => { + return ; + }, []); + const separators = useMemo(() => { + return + + ; + }, []); + const offsets = useMemo(() => items.map((_, i) => i * itemHeight), [items, itemHeight]); + return + {separators} + + + + + + {label && labelContainer} + {fader(FaderPosition.BOTTOM)} + {fader(FaderPosition.TOP)} + ; +}; +WheelPicker.alignments = WheelPickerAlign; +export default WheelPicker; +export { WheelPickerItemProps }; +const styles = StyleSheet.create({ + gestureContainer: { + flexGrow: 1 + }, + separators: { + borderTopWidth: 1, + borderBottomWidth: 1, + height: Spacings.s9, + borderColor: Colors.$outlineDefault + }, + label: { + position: 'absolute', + top: 0, + bottom: 0 + } +}); \ No newline at end of file diff --git a/src/components/WheelPicker/types.d.ts b/src/components/WheelPicker/types.d.ts new file mode 100644 index 0000000000..10d9a4e4e4 --- /dev/null +++ b/src/components/WheelPicker/types.d.ts @@ -0,0 +1,6 @@ +export declare enum WheelPickerAlign { + CENTER = "center", + RIGHT = "right", + LEFT = "left" +} +export type WheelPickerItemValue = number | string; diff --git a/src/components/WheelPicker/types.js b/src/components/WheelPicker/types.js new file mode 100644 index 0000000000..a12fbe6124 --- /dev/null +++ b/src/components/WheelPicker/types.js @@ -0,0 +1,6 @@ +export let WheelPickerAlign = /*#__PURE__*/function (WheelPickerAlign) { + WheelPickerAlign["CENTER"] = "center"; + WheelPickerAlign["RIGHT"] = "right"; + WheelPickerAlign["LEFT"] = "left"; + return WheelPickerAlign; +}({}); \ No newline at end of file diff --git a/src/components/WheelPicker/usePresenter.d.ts b/src/components/WheelPicker/usePresenter.d.ts new file mode 100644 index 0000000000..61cdc53a17 --- /dev/null +++ b/src/components/WheelPicker/usePresenter.d.ts @@ -0,0 +1,21 @@ +/// +import { WheelPickerItemValue } from './types'; +import { WheelPickerItemProps } from './Item'; +type PropTypes = { + initialValue?: T; + children?: JSX.Element | JSX.Element[]; + items?: WheelPickerItemProps[]; + itemHeight: number; + preferredNumVisibleRows: number; +}; +type RowItem = { + value: T; + index: number; +}; +declare const usePresenter: ({ initialValue, children, items: propItems, itemHeight, preferredNumVisibleRows }: PropTypes) => { + index: number | undefined; + items: WheelPickerItemProps[]; + height: number; + getRowItemAtOffset: (offset: number) => RowItem; +}; +export default usePresenter; diff --git a/src/components/WheelPicker/usePresenter.js b/src/components/WheelPicker/usePresenter.js new file mode 100644 index 0000000000..29229f33d2 --- /dev/null +++ b/src/components/WheelPicker/usePresenter.js @@ -0,0 +1,57 @@ +import _findIndex from "lodash/findIndex"; +import _isObject from "lodash/isObject"; +import React from 'react'; +import { LogService } from "../../services"; +import useMiddleIndex from "./helpers/useListMiddleIndex"; + +//TODO: deprecate this type + +const usePresenter = ({ + initialValue, + children, + items: propItems, + itemHeight, + preferredNumVisibleRows +}) => { + const extractItemsFromChildren = () => { + const items = React.Children.map(children, child => { + const childAsType = { + value: child?.props.value, + label: child?.props.label + }; + return childAsType; + }); + return items || []; + }; + const items = children ? extractItemsFromChildren() : propItems || []; + const middleIndex = useMiddleIndex({ + itemHeight, + listSize: items.length + }); + const getSelectedValueIndex = () => { + if (_isObject(initialValue)) { + LogService.warn('UILib WheelPicker will stop supporting initialValue prop type as an object (ItemProps). Please pass string or number only'); + //@ts-expect-error + return _findIndex(items, { + value: initialValue?.value + }); + } else { + return initialValue && _findIndex(items, item => item.value === initialValue); + } + }; + const getRowItemAtOffset = offset => { + const index = middleIndex(offset); + const value = items[index].value; + return { + value, + index + }; + }; + return { + index: getSelectedValueIndex(), + items, + height: itemHeight * preferredNumVisibleRows, + getRowItemAtOffset + }; +}; +export default usePresenter; \ No newline at end of file diff --git a/src/components/actionBar/index.d.ts b/src/components/actionBar/index.d.ts new file mode 100644 index 0000000000..099e1d00ab --- /dev/null +++ b/src/components/actionBar/index.d.ts @@ -0,0 +1,35 @@ +import React from 'react'; +import { ViewStyle } from 'react-native'; +import { ButtonProps } from '../button'; +export type ActionBarProps = { + /** + * action bar height + */ + height?: number; + /** + * action bar background color + */ + backgroundColor?: string; + /** + * actions for the action bar + */ + actions: ButtonProps[]; + /** + * should action be equally centered + */ + centered?: boolean; + /** + * use safe area, in case action bar attached to the bottom (default: true) + */ + useSafeArea?: boolean; + /** + * keep the action bar position relative instead of it absolute position + */ + keepRelative?: boolean; + /** + * style the action bar + */ + style?: ViewStyle; +}; +declare const _default: React.ForwardRefExoticComponent>; +export default _default; diff --git a/src/components/actionBar/index.js b/src/components/actionBar/index.js new file mode 100644 index 0000000000..9b39a83e33 --- /dev/null +++ b/src/components/actionBar/index.js @@ -0,0 +1,69 @@ +import _map from "lodash/map"; +import React, { Component } from 'react'; +import { StyleSheet } from 'react-native'; +import { Colors, Shadows } from "../../style"; +import { asBaseComponent } from "../../commons/new"; +import View from "../view"; +import Button from "../button"; +/** + * @description: Quick actions bar, each action support Button component props + * @modifiers: margin, padding + * @gif: https://github.com/wix/react-native-ui-lib/blob/master/demo/showcase/ActionBar/ActionBar.gif?raw=true + * @example: https://github.com/wix/react-native-ui-lib/blob/master/demo/src/screens/componentScreens/ActionBarScreen.tsx + */ +class ActionBar extends Component { + static displayName = 'ActionBar'; + static defaultProps = { + height: 48, + backgroundColor: Colors.$backgroundElevated, + useSafeArea: true + }; + styles = createStyles(this.props); + getAlignment(actionIndex) { + const { + actions, + centered + } = this.props; + const first = actionIndex === 0; + const last = actionIndex === actions.length - 1; + return { + left: centered ? false : first, + center: centered || !first && !last || first && last, + right: centered ? false : last + }; + } + render() { + const { + actions, + centered, + style, + useSafeArea, + keepRelative, + ...others + } = this.props; + return + + {_map(actions, (action, i) => + + ; +} +const StatefulScreen = () => ; +const StatefulScreenWithTextsAndButtons = () => { + const [count1, setCount1] = useState(0); + const [count2, setCount2] = useState(0); + return + {`button 1 pressed ${count1} times`} + {`button 2 pressed ${count2} times`} +