diff --git a/.editorconfig b/.editorconfig index b5cbd764..11c19b68 100644 --- a/.editorconfig +++ b/.editorconfig @@ -9,6 +9,6 @@ insert_final_newline = true max_line_length = 120 trim_trailing_whitespace = true -[*.{sh,json,podspec}] +[*.{sh,json,podspec,yml,yaml}] indent_style = space indent_size = 2 diff --git a/.eslintrc b/.eslintrc index 4aef2977..d2cfe29f 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,7 +8,9 @@ "@typescript-eslint/no-explicit-any": 0, "@typescript-eslint/no-unused-vars": 0, "@typescript-eslint/ban-types": 0, + "@typescript-eslint/no-empty-interface": 0, "no-duplicate-imports": 0, - "functional/immutable-data": 0 + "functional/immutable-data": 0, + "no-underscore-dangle": 0 } } diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 30380976..6872330f 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -7,10 +7,10 @@ body: value: | Thanks for taking the time to fill out this bug report! - Before submitting a new bug/issue, please check the links below to see if there is a solution or question posted there already: - - Issues tab: https://github.com/jpudysz/react-native-unistyles/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc - - Closed issues tab: https://github.com/jpudysz/react-native-unistyles/issues?q=is%3Aissue+is%3Aclosed - - Discussions tab: https://github.com/jpudysz/react-native-unistyles/discussions + Before submitting a new bug/issue, please check the links below to see if there is a solution or question posted there already: + - Issues tab: https://github.com/jpudysz/react-native-unistyles/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc + - Closed issues tab: https://github.com/jpudysz/react-native-unistyles/issues?q=is%3Aissue+is%3Aclosed + - Discussions tab: https://github.com/jpudysz/react-native-unistyles/discussions - type: markdown attributes: value: | @@ -50,7 +50,7 @@ body: attributes: label: Unistyles version description: What version of react-native-unistyles are you using? - placeholder: 1.0.0 + placeholder: 2.0.0 validations: required: true @@ -73,5 +73,27 @@ body: - Android - iOS - React Native Web + - SSR + - React Native Windows + + - type: dropdown + id: engine + attributes: + label: Engine + description: Which engine do you use? + multiple: false + options: + - Hermes + - JSC + + - type: dropdown + id: architecture + attributes: + label: Architecture + description: Which architecture do you use? + multiple: false + options: + - Paper (old) + - Fabric (new) validations: required: true diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 60a322da..12328911 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,5 +1,3 @@ - - ## Summary diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 562009fd..a8d240c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ jobs: - name: Checkout uses: actions/checkout@v3 with: - fetch-depth: 0 + fetch-depth: 0 - name: Setup uses: ./.github/actions/setup diff --git a/.gitignore b/.gitignore index 1ace69ef..00cd794e 100644 --- a/.gitignore +++ b/.gitignore @@ -42,7 +42,8 @@ android.iml # Cocoapods # -examples/expo/ios/Pods +examples/expo/ios +examples/expo/android # Ruby examples/expo/vendor/ @@ -81,3 +82,4 @@ example/expo/.yarn #docs docs/.yarn coverage +docs/dist diff --git a/README.md b/README.md index 78c84396..1f2c1a43 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,42 @@ -[react-native-unistyles](https://codemask.com) - - - - - react-native-unistyles - +[react-native-unistyles](https://reactnativeunistyles.vercel.app/) ![GitHub package.json version](https://img.shields.io/github/package-json/v/jpudysz/react-native-unistyles?style=for-the-badge) [![npm downloads](https://img.shields.io/npm/dm/react-native-unistyles.svg?style=for-the-badge)](https://www.npmjs.com/package/react-native-unistyles) ![Platform - Android and iOS](https://img.shields.io/badge/platform-Android%20%7C%20IOS%20%7C%20SSR%20%7C%20Web-blue.svg?style=for-the-badge) [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge)](https://opensource.org/licenses/MIT) +## [Documentation](https://reactnativeunistyles.vercel.app/) +- [Start here](https://reactnativeunistyles.vercel.app/start/introduction/) +- [API](https://reactnativeunistyles.vercel.app/reference/create-stylesheet/) +- [Show case](https://reactnativeunistyles.vercel.app/show-case/projects/) +- [Examples](https://reactnativeunistyles.vercel.app/examples/all/) + ## Features -- ⚡ Blazing fast, adds around ~3ms on top of StyleSheet* +- 🚀 Shared core with C++ and JSI bindings +- 🌉 Supports new architecture +- 🔥 Crazy performance, adds under 0.1 ms to your StyleSheet - 🎳 Share up to 100% of your styles across platforms in monorepo -- 🎯 Doesn't introduce new components -- 🖥️ Supports custom breakpoints and css-like media queries -- 🎨 Access theme in your StyleSheets and components -- 🪄 Supports dynamic functions to access values from JSX -- 🥳 Compatible with Expo, Expo Go, Bare React Native, React Native Web and SSR +- 🎯 Doesn't introduce new components, everything is packed in one hook +- ⚛️ No React Context +- 🖥️ Supports custom breakpoints, css-like media queries and variants +- 🎨 Register multiple themes and change them with single function call +- 🥳 Compatible with Expo, Bare React Native, React Native Web and SSR - 🛡️ ~99% Test coverage +- 🔌 Extend stylesheets with your own plugins - ⚔️ No 3rd party dependencies - -*-based on this [benchmark](https://github.com/efstathiosntonas/react-native-style-libraries-benchmark) +- and much much more! ## Installation -```cmd -yarn add react-native-unistyles -``` - -## [Documentation](https://reactnativeunistyles.vercel.app/) -- [Start here](https://reactnativeunistyles.vercel.app/start/setup/) -- [References](https://reactnativeunistyles.vercel.app/reference/create-stylesheet/) -- [Examples](https://reactnativeunistyles.vercel.app/example/breakpoints/) - -## Faster, better and simpler - v.2.0 🚀 - -There is ongoing work on branch [2.0](https://github.com/jpudysz/react-native-unistyles/tree/2.0). - ---- - -Join early testers and discussion [here](https://github.com/jpudysz/react-native-unistyles/discussions/41). - -```cmd +```shell +# Unistyles is currently in RC phase yarn add react-native-unistyles@next ``` -## Blog post +## Discord +Looking for help or you want to chat with me? -Read about what drove me to create this library in this blog post [here](https://www.reactnativecrossroads.com/posts/level-up-react-native-styles). +[Join Discord](https://discord.gg/akGHf27P4C) ## Sponsor my work diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt new file mode 100644 index 00000000..f438e6bb --- /dev/null +++ b/android/CMakeLists.txt @@ -0,0 +1,27 @@ +cmake_minimum_required(VERSION 3.9.0) + +project(unistyles) + +add_library(unistyles + SHARED + ../cxx/UnistylesRuntime.cpp + ./src/main/cxx/cpp-adapter.cpp +) + +include_directories( + ../cxx +) + +set_target_properties(unistyles PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + POSITION_INDEPENDENT_CODE ON +) + +find_package(ReactAndroid REQUIRED CONFIG) + +target_link_libraries(unistyles + ReactAndroid::jsi + android +) diff --git a/android/build.gradle b/android/build.gradle new file mode 100644 index 00000000..13ababc8 --- /dev/null +++ b/android/build.gradle @@ -0,0 +1,75 @@ +buildscript { + ext.safeExtGet = {prop, fallback -> + rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback + } + repositories { + google() + gradlePluginPortal() + } + dependencies { + classpath("com.android.tools.build:gradle:7.3.1") + classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.22") + } +} + +apply plugin: 'com.android.library' +apply plugin: 'org.jetbrains.kotlin.android' + +def resolveBuildType() { + Gradle gradle = getGradle() + String tskReqStr = gradle.getStartParameter().getTaskRequests()['args'].toString() + + return tskReqStr.contains('Release') ? 'release' : 'debug' +} + +def isNewArchitectureEnabled() { + return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true" +} + +if (isNewArchitectureEnabled()) { + apply plugin: 'com.facebook.react' +} + +android { + compileSdkVersion safeExtGet('compileSdkVersion', 33) + namespace "com.unistyles" + + defaultConfig { + buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString() + minSdkVersion safeExtGet('minSdkVersion', 21) + externalNativeBuild { + cmake { + arguments "-DANDROID_STL=c++_shared" + } + } + } + + buildFeatures { + prefab true + } + + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } + + packagingOptions { + doNotStrip resolveBuildType() == 'debug' ? "**/**/*.so" : '' + excludes = [ + "META-INF", + "META-INF/**", + "**/libjsi.so", + "**/libc++_shared.so" + ] + } +} + +repositories { + mavenCentral() + google() +} + +dependencies { + implementation 'com.facebook.react:react-native' +} diff --git a/android/src/main/cxx/cpp-adapter.cpp b/android/src/main/cxx/cpp-adapter.cpp new file mode 100644 index 00000000..29be2137 --- /dev/null +++ b/android/src/main/cxx/cpp-adapter.cpp @@ -0,0 +1,108 @@ +#include +#include +#include "UnistylesRuntime.h" + +using namespace facebook; + +static jobject unistylesModule = nullptr; +std::shared_ptr unistylesRuntime = nullptr; + +void throwKotlinException( + JNIEnv *env, + const char *message +) { + jclass runtimeExceptionClass = env->FindClass("java/lang/RuntimeException"); + + if (runtimeExceptionClass != nullptr) { + env->ThrowNew(runtimeExceptionClass, message); + env->DeleteLocalRef(runtimeExceptionClass); + } +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_unistyles_UnistylesModule_nativeInstall( + JNIEnv *env, + jobject thiz, + jlong jsi, + jint screenWidth, + jint screenHeight, + jstring colorScheme +) { + auto runtime = reinterpret_cast(jsi); + + if (unistylesModule == nullptr) { + unistylesModule = env->NewGlobalRef(thiz); + } + + if (runtime == nullptr || unistylesModule == nullptr) { + return throwKotlinException(env, "Something went wrong while initializing UnistylesModule"); + } + + const char *colorSchemeChars = env->GetStringUTFChars(colorScheme, nullptr); + std::string colorSchemeStr(colorSchemeChars); + env->ReleaseStringUTFChars(colorScheme, colorSchemeChars); + + unistylesRuntime = std::make_shared( + screenWidth, + screenHeight, + colorSchemeStr + ); + + unistylesRuntime->onThemeChange([=](const std::string &theme) { + jstring themeStr = env->NewStringUTF(theme.c_str()); + jclass cls = env->GetObjectClass(unistylesModule); + jmethodID methodId = env->GetMethodID(cls, "onThemeChange", "(Ljava/lang/String;)V"); + + env->CallVoidMethod(unistylesModule, methodId, themeStr); + env->DeleteLocalRef(themeStr); + env->DeleteLocalRef(cls); + }); + + unistylesRuntime->onLayoutChange([=](const std::string &breakpoint, const std::string &orientation, int width, int height) { + jstring breakpointStr = env->NewStringUTF(breakpoint.c_str()); + jstring orientationStr = env->NewStringUTF(orientation.c_str()); + jclass cls = env->GetObjectClass(unistylesModule); + jmethodID methodId = env->GetMethodID(cls, "onLayoutChange", "(Ljava/lang/String;Ljava/lang/String;II)V"); + + env->CallVoidMethod(unistylesModule, methodId, breakpointStr, orientationStr, width, height); + env->DeleteLocalRef(breakpointStr); + env->DeleteLocalRef(orientationStr); + env->DeleteLocalRef(cls); + }); + + unistylesRuntime->onPluginChange([=]() { + jclass cls = env->GetObjectClass(unistylesModule); + jmethodID methodId = env->GetMethodID(cls, "onPluginChange", "()V"); + + env->CallVoidMethod(unistylesModule, methodId); + env->DeleteLocalRef(cls); + }); + + jsi::Object hostObject = jsi::Object::createFromHostObject(*runtime, unistylesRuntime); + + runtime->global().setProperty(*runtime, "__UNISTYLES__", std::move(hostObject)); +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_unistyles_UnistylesModule_nativeDestroy(JNIEnv *env, jobject thiz) { + unistylesRuntime.reset(); + unistylesModule = nullptr; +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_unistyles_UnistylesModule_nativeOnOrientationChange(JNIEnv *env, jobject thiz, jint width, jint height) { + if (unistylesRuntime != nullptr) { + unistylesRuntime->handleScreenSizeChange(width, height); + } +} + +extern "C" +JNIEXPORT void JNICALL +Java_com_unistyles_UnistylesModule_nativeOnAppearanceChange(JNIEnv *env, jobject thiz, jstring colorScheme) { + if (unistylesRuntime != nullptr) { + unistylesRuntime->handleAppearanceChange(env->GetStringUTFChars(colorScheme, nullptr)); + } +} diff --git a/android/src/main/java/com/unistyles/UnistylesModule.kt b/android/src/main/java/com/unistyles/UnistylesModule.kt new file mode 100644 index 00000000..e8183fcc --- /dev/null +++ b/android/src/main/java/com/unistyles/UnistylesModule.kt @@ -0,0 +1,163 @@ +package com.unistyles + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.res.Configuration +import android.os.Handler +import android.os.Looper +import android.util.Log +import com.facebook.react.bridge.Arguments +import com.facebook.react.bridge.LifecycleEventListener +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.bridge.ReactContextBaseJavaModule +import com.facebook.react.bridge.ReactMethod +import com.facebook.react.modules.core.DeviceEventManagerModule + +class UnistylesModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext), LifecycleEventListener { + private val configurationChangeReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + if (intent.action == Intent.ACTION_CONFIGURATION_CHANGED) { + Handler(Looper.getMainLooper()).postDelayed({ + this@UnistylesModule.onConfigChange() + }, 10) + } + } + } + + override fun getName() = NAME + companion object { + const val NAME = "Unistyles" + } + + //region Lifecycle + init { + reactApplicationContext.registerReceiver(configurationChangeReceiver, IntentFilter(Intent.ACTION_CONFIGURATION_CHANGED)) + } + + override fun onCatalystInstanceDestroy() { + reactApplicationContext.unregisterReceiver(configurationChangeReceiver) + this.nativeDestroy() + } + + //endregion + //region Event handlers + private fun onConfigChange() { + val config = this.getConfig() + + reactApplicationContext.runOnJSQueueThread { + this.nativeOnOrientationChange( + config["width"] as Int, + config["height"] as Int + ) + this.nativeOnAppearanceChange( + config["colorScheme"] as String + ) + } + } + + private fun getConfig(): Map { + val displayMetrics = reactApplicationContext.resources.displayMetrics + val colorScheme = when (reactApplicationContext.resources.configuration.uiMode.and(Configuration.UI_MODE_NIGHT_MASK)) { + Configuration.UI_MODE_NIGHT_YES -> "dark" + Configuration.UI_MODE_NIGHT_NO -> "light" + else -> "unspecified" + } + + return mapOf( + "width" to (displayMetrics.widthPixels / displayMetrics.density).toInt(), + "height" to (displayMetrics.heightPixels / displayMetrics.density).toInt(), + "colorScheme" to colorScheme + ) + } + + //endregion + //region Core + @ReactMethod(isBlockingSynchronousMethod = true) + fun install(): Boolean { + return try { + System.loadLibrary("unistyles") + val config = this.getConfig() + + this.reactApplicationContext.javaScriptContextHolder?.let { + this.nativeInstall( + it.get(), + config["width"] as Int, + config["height"] as Int, + config["colorScheme"] as String + ) + + Log.i(NAME, "Installed Unistyles \uD83E\uDD84!") + + return true + } + + false + } catch (e: Exception) { + false + } + } + + private external fun nativeInstall(jsi: Long, width: Int, height: Int, colorScheme: String) + private external fun nativeDestroy() + private external fun nativeOnOrientationChange(width: Int, height: Int) + private external fun nativeOnAppearanceChange(colorScheme: String) + + //endregion + //region Event emitter + private fun onLayoutChange(breakpoint: String, orientation: String, width: Int, height: Int) { + val body = Arguments.createMap().apply { + putString("type", "layout") + putMap("payload", Arguments.createMap().apply { + putString("breakpoint", breakpoint) + putString("orientation", orientation) + putMap("screen", Arguments.createMap().apply { + putInt("width", width) + putInt("height", height) + }) + }) + } + + reactApplicationContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + .emit("__unistylesOnChange", body) + } + + private fun onThemeChange(themeName: String) { + val body = Arguments.createMap().apply { + putString("type", "theme") + putMap("payload", Arguments.createMap().apply { + putString("themeName", themeName) + }) + } + + reactApplicationContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + .emit("__unistylesOnChange", body) + } + + private fun onPluginChange() { + val body = Arguments.createMap().apply { + putString("type", "plugin") + } + + reactApplicationContext + .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java) + .emit("__unistylesOnChange", body) + } + + @ReactMethod + fun addListener(eventName: String?) = Unit + + @ReactMethod + fun removeListeners(count: Double) = Unit + override fun onHostResume() { + this.onConfigChange() + } + + override fun onHostPause() {} + + override fun onHostDestroy() {} + //endregion +} diff --git a/android/src/main/java/com/unistyles/UnistylesPackage.kt b/android/src/main/java/com/unistyles/UnistylesPackage.kt new file mode 100644 index 00000000..1a3332b3 --- /dev/null +++ b/android/src/main/java/com/unistyles/UnistylesPackage.kt @@ -0,0 +1,18 @@ +package com.unistyles + +import android.view.View +import com.facebook.react.ReactPackage +import com.facebook.react.bridge.NativeModule +import com.facebook.react.bridge.ReactApplicationContext +import com.facebook.react.uimanager.ReactShadowNode +import com.facebook.react.uimanager.ViewManager + +class UnistylesPackage: ReactPackage { + override fun createNativeModules(reactContext: ReactApplicationContext): MutableList { + return mutableListOf(UnistylesModule(reactContext)) + } + + override fun createViewManagers(reactContext: ReactApplicationContext): MutableList>> { + return mutableListOf() + } +} diff --git a/assets/banner.png b/assets/banner.png index 7d54399b..e4852a00 100644 Binary files a/assets/banner.png and b/assets/banner.png differ diff --git a/assets/uni-dark.svg b/assets/uni-dark.svg deleted file mode 100644 index 9d9e66c9..00000000 --- a/assets/uni-dark.svg +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/assets/uni-light.svg b/assets/uni-light.svg deleted file mode 100644 index cc664e22..00000000 --- a/assets/uni-light.svg +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/cxx/UnistylesRuntime.cpp b/cxx/UnistylesRuntime.cpp new file mode 100644 index 00000000..4c1d0ecc --- /dev/null +++ b/cxx/UnistylesRuntime.cpp @@ -0,0 +1,308 @@ +#include "UnistylesRuntime.h" + +#include +#include + +#pragma region HostObject + +std::vector UnistylesRuntime::getPropertyNames(jsi::Runtime& runtime) { + std::vector properties; + + // getters + properties.push_back(jsi::PropNameID::forUtf8(runtime, std::string("screenWidth"))); + properties.push_back(jsi::PropNameID::forUtf8(runtime, std::string("screenHeight"))); + properties.push_back(jsi::PropNameID::forUtf8(runtime, std::string("hasAdaptiveThemes"))); + properties.push_back(jsi::PropNameID::forUtf8(runtime, std::string("themeName"))); + properties.push_back(jsi::PropNameID::forUtf8(runtime, std::string("breakpoint"))); + properties.push_back(jsi::PropNameID::forUtf8(runtime, std::string("colorScheme"))); + properties.push_back(jsi::PropNameID::forUtf8(runtime, std::string("sortedBreakpointPairs"))); + properties.push_back(jsi::PropNameID::forUtf8(runtime, std::string("useBreakpoints"))); + properties.push_back(jsi::PropNameID::forUtf8(runtime, std::string("useTheme"))); + properties.push_back(jsi::PropNameID::forUtf8(runtime, std::string("useAdaptiveThemes"))); + properties.push_back(jsi::PropNameID::forUtf8(runtime, std::string("addPlugin"))); + properties.push_back(jsi::PropNameID::forUtf8(runtime, std::string("removePlugin"))); + properties.push_back(jsi::PropNameID::forUtf8(runtime, std::string("enabledPlugins"))); + + // setters + properties.push_back(jsi::PropNameID::forUtf8(runtime, std::string("themes"))); + + return properties; +} + +jsi::Value UnistylesRuntime::get(jsi::Runtime& runtime, const jsi::PropNameID& propNameId) { + std::string propName = propNameId.utf8(runtime); + + if (propName == "screenWidth") { + return jsi::Value(this->screenWidth); + } + + if (propName == "screenHeight") { + return jsi::Value(this->screenHeight); + } + + if (propName == "hasAdaptiveThemes") { + return jsi::Value(this->hasAdaptiveThemes); + } + + if (propName == "themeName") { + return !this->themeName.empty() + ? jsi::Value(jsi::String::createFromUtf8(runtime, this->themeName)) + : this->getThemeOrFail(runtime); + } + + if (propName == "breakpoint") { + return !this->breakpoint.empty() + ? jsi::Value(jsi::String::createFromUtf8(runtime, this->breakpoint)) + : jsi::Value::undefined(); + } + + if (propName == "colorScheme") { + return jsi::Value(jsi::String::createFromUtf8(runtime, this->colorScheme)); + } + + if (propName == "enabledPlugins") { + auto jsiArray = facebook::jsi::Array(runtime, this->pluginNames.size()); + + for (size_t i = 0; i < this->pluginNames.size(); i++) { + jsiArray.setValueAtIndex(runtime, i, facebook::jsi::String::createFromUtf8(runtime, this->pluginNames[i])); + } + + return jsiArray; + } + + if (propName == "sortedBreakpointPairs") { + std::unique_ptr sortedBreakpointEntriesArray = std::make_unique(runtime, this->sortedBreakpointPairs.size()); + + for (size_t i = 0; i < this->sortedBreakpointPairs.size(); ++i) { + std::unique_ptr pairArray = std::make_unique(runtime, 2); + jsi::String nameValue = jsi::String::createFromUtf8(runtime, this->sortedBreakpointPairs[i].first); + + pairArray->setValueAtIndex(runtime, 0, nameValue); + pairArray->setValueAtIndex(runtime, 1, jsi::Value(this->sortedBreakpointPairs[i].second)); + sortedBreakpointEntriesArray->setValueAtIndex(runtime, i, *pairArray); + } + + return jsi::Value(runtime, *sortedBreakpointEntriesArray); + } + + if (propName == "addPlugin") { + return jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forAscii(runtime, "addPlugin"), + 1, + [this](jsi::Runtime &runtime, const jsi::Value &thisVal, const jsi::Value *arguments, size_t count) -> jsi::Value { + std::string pluginName = arguments[0].asString(runtime).utf8(runtime); + bool notify = arguments[1].asBool(); + + this->pluginNames.push_back(pluginName); + + // registry enabled plugins won't notify listeners + if (notify) { + this->onPluginChangeCallback(); + } + + return jsi::Value::undefined(); + } + ); + } + + if (propName == "removePlugin") { + return jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forAscii(runtime, "removePlugin"), + 1, + [this](jsi::Runtime &runtime, const jsi::Value &thisVal, const jsi::Value *arguments, size_t count) -> jsi::Value { + std::string pluginName = arguments[0].asString(runtime).utf8(runtime); + + auto it = std::find(this->pluginNames.begin(), this->pluginNames.end(), pluginName); + + if (it != this->pluginNames.end()) { + this->pluginNames.erase(it); + this->onPluginChangeCallback(); + } + + return jsi::Value::undefined(); + } + ); + } + + if (propName == "useBreakpoints") { + return jsi::Function::createFromHostFunction( + runtime, + jsi::PropNameID::forAscii(runtime, "useBreakpoints"), + 1, + [this](jsi::Runtime &runtime, const jsi::Value &thisVal, const jsi::Value *arguments, size_t count) -> jsi::Value { + jsi::Object breakpointsObj = arguments[0].asObject(runtime); + jsi::Array propertyNames = breakpointsObj.getPropertyNames(runtime); + std::vector> sortedBreakpointEntriesVec; + + for (size_t i = 0; i < propertyNames.size(runtime); ++i) { + jsi::Value propNameValue = propertyNames.getValueAtIndex(runtime, i); + std::string name = propNameValue.asString(runtime).utf8(runtime); + jsi::PropNameID propNameID = jsi::PropNameID::forUtf8(runtime, name); + jsi::Value value = breakpointsObj.getProperty(runtime, propNameID); + + if (value.isNumber()) { + double breakpointValue = value.asNumber(); + sortedBreakpointEntriesVec.push_back(std::make_pair(name, breakpointValue)); + } + } + + std::sort(sortedBreakpointEntriesVec.begin(), sortedBreakpointEntriesVec.end(), [](const std::pair& a, const std::pair& b) { + return a.second < b.second; + }); + + if (sortedBreakpointEntriesVec.size() == 0) { + throw jsi::JSError(runtime, UnistylesErrorBreakpointsCannotBeEmpty); + } + + if (sortedBreakpointEntriesVec.at(0).second != 0) { + throw jsi::JSError(runtime, UnistylesErrorBreakpointsMustStartFromZero); + } + + this->sortedBreakpointPairs = sortedBreakpointEntriesVec; + + std::string breakpoint = this->getBreakpointFromScreenWidth(this->screenWidth, sortedBreakpointEntriesVec); + + this->breakpoint = breakpoint; + + return jsi::Value::undefined(); + } + ); + } + + if (propName == "useTheme") { + return jsi::Function::createFromHostFunction(runtime, + jsi::PropNameID::forAscii(runtime, "useTheme"), + 1, + [this](jsi::Runtime &runtime, const jsi::Value &thisVal, const jsi::Value *arguments, size_t count) -> jsi::Value { + std::string themeName = arguments[0].asString(runtime).utf8(runtime); + + if (this->themeName != themeName) { + this->themeName = themeName; + this->onThemeChangeCallback(themeName); + } + + return jsi::Value::undefined(); + } + ); + } + + if (propName == "useAdaptiveThemes") { + return jsi::Function::createFromHostFunction(runtime, + jsi::PropNameID::forAscii(runtime, "useAdaptiveThemes"), + 1, + [this](jsi::Runtime &runtime, const jsi::Value &thisVal, const jsi::Value *arguments, size_t count) -> jsi::Value { + bool enableAdaptiveThemes = arguments[0].asBool(); + + this->hasAdaptiveThemes = enableAdaptiveThemes; + + if (!enableAdaptiveThemes || !this->supportsAutomaticColorScheme) { + return jsi::Value::undefined(); + } + + if (this->themeName != this->colorScheme) { + this->themeName = this->colorScheme; + this->onThemeChangeCallback(this->themeName); + } + + return jsi::Value::undefined(); + } + ); + } + + return jsi::Value::undefined(); +} + +void UnistylesRuntime::set(jsi::Runtime& runtime, const jsi::PropNameID& propNameId, const jsi::Value& value) { + std::string propName = propNameId.utf8(runtime); + + if (propName == "themes" && value.isObject()) { + jsi::Array themes = value.asObject(runtime).asArray(runtime); + std::vector themesVector; + size_t length = themes.size(runtime); + + for (size_t i = 0; i < length; ++i) { + jsi::Value element = themes.getValueAtIndex(runtime, i); + + if (element.isString()) { + std::string theme = element.asString(runtime).utf8(runtime); + themesVector.push_back(theme); + } + } + + if (themesVector.size() == 0) { + throw jsi::JSError(runtime, UnistylesErrorThemesCannotBeEmpty); + } + + this->themes = themesVector; + this->themeName = ""; + + bool hasLightTheme = std::find(themesVector.begin(), themesVector.end(), "light") != themesVector.end(); + bool hasDarkTheme = std::find(themesVector.begin(), themesVector.end(), "dark") != themesVector.end(); + + this->supportsAutomaticColorScheme = hasLightTheme && hasDarkTheme; + + return; + } +} + +#pragma endregion +#pragma region Helpers + +std::string UnistylesRuntime::getBreakpointFromScreenWidth(int width, const std::vector>& sortedBreakpointPairs) { + for (size_t i = 0; i < sortedBreakpointPairs.size(); ++i) { + const auto& [key, value] = sortedBreakpointPairs[i]; + const double maxVal = (i + 1 < sortedBreakpointPairs.size()) ? sortedBreakpointPairs[i + 1].second : std::numeric_limits::infinity(); + + if (width >= value && width < maxVal) { + return key; + } + } + + return sortedBreakpointPairs.empty() ? "" : sortedBreakpointPairs.back().first; +} + +void UnistylesRuntime::handleScreenSizeChange(int width, int height) { + std::string breakpoint = this->getBreakpointFromScreenWidth(width, this->sortedBreakpointPairs); + bool shouldNotify = this->breakpoint != breakpoint || this->screenWidth != width || this->screenHeight != height; + + this->breakpoint = breakpoint; + this->screenWidth = width; + this->screenHeight = height; + + std::string orientation = width > height + ? UnistylesOrientationLandscape + : UnistylesOrientationPortrait; + + if (shouldNotify) { + this->onLayoutChangeCallback(breakpoint, orientation, width, height); + } +} + +void UnistylesRuntime::handleAppearanceChange(std::string colorScheme) { + this->colorScheme = colorScheme; + + if (!this->supportsAutomaticColorScheme || !this->hasAdaptiveThemes) { + return; + } + + if (this->themeName != this->colorScheme) { + this->onThemeChangeCallback(this->colorScheme); + this->themeName = this->colorScheme; + } +} + +jsi::Value UnistylesRuntime::getThemeOrFail(jsi::Runtime& runtime) { + if (this->themes.size() == 1) { + std::string themeName = this->themes.at(0); + + this->themeName = themeName; + + return jsi::String::createFromUtf8(runtime, themeName); + } + + return jsi::Value().undefined(); +} + +#pragma endregion diff --git a/cxx/UnistylesRuntime.h b/cxx/UnistylesRuntime.h new file mode 100644 index 00000000..3e5a4237 --- /dev/null +++ b/cxx/UnistylesRuntime.h @@ -0,0 +1,66 @@ +#pragma once + +#include +#include + +using namespace facebook; + +const std::string UnistylesOrientationPortrait = "portrait"; +const std::string UnistylesOrientationLandscape = "landscape"; + +const std::string UnistylesDarkScheme = "dark"; +const std::string UnistylesLightScheme = "light"; +const std::string UnistylesUnspecifiedScheme = "unspecified"; + +const std::string UnistylesErrorBreakpointsCannotBeEmpty = "You are trying to register empty breakpoints object"; +const std::string UnistylesErrorBreakpointsMustStartFromZero = "You are trying to register breakpoints that don't start from 0"; +const std::string UnistylesErrorThemesCannotBeEmpty = "You are trying to register empty themes object"; + +class JSI_EXPORT UnistylesRuntime : public jsi::HostObject { +private: + std::function onThemeChangeCallback; + std::function onLayoutChangeCallback; + std::function onPluginChangeCallback; + + int screenWidth; + int screenHeight; + std::string colorScheme; + +public: + UnistylesRuntime( + int screenWidth, + int screenHeight, + std::string colorScheme + ): screenWidth(screenWidth), screenHeight(screenHeight), colorScheme(colorScheme) {} + + bool hasAdaptiveThemes; + bool supportsAutomaticColorScheme; + + std::string themeName; + std::string breakpoint; + std::vector pluginNames; + std::vector themes; + std::vector> sortedBreakpointPairs; + + void onThemeChange(std::function callback) { + this->onThemeChangeCallback = callback; + } + + void onLayoutChange(std::function callback) { + this->onLayoutChangeCallback = callback; + } + + void onPluginChange(std::function callback) { + this->onPluginChangeCallback = callback; + } + + jsi::Value get(jsi::Runtime&, const jsi::PropNameID& name) override; + void set(jsi::Runtime& runtime, const jsi::PropNameID& propNameId, const jsi::Value& value) override; + std::vector getPropertyNames(jsi::Runtime& runtime) override; + + void handleScreenSizeChange(int width, int height); + void handleAppearanceChange(std::string colorScheme); + + jsi::Value getThemeOrFail(jsi::Runtime&); + std::string getBreakpointFromScreenWidth(int width, const std::vector>& sortedBreakpointEntries); +}; diff --git a/docs/.astro/types.d.ts b/docs/.astro/types.d.ts index ee2e2301..185f52fa 100644 --- a/docs/.astro/types.d.ts +++ b/docs/.astro/types.d.ts @@ -185,51 +185,121 @@ declare module 'astro:content' { type ContentEntryMap = { "docs": { -"example/breakpoints.mdx": { - id: "example/breakpoints.mdx"; - slug: "example/breakpoints"; +"examples/all.mdx": { + id: "examples/all.mdx"; + slug: "examples/all"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".mdx"] }; -"example/dynamic-functions.mdx": { - id: "example/dynamic-functions.mdx"; - slug: "example/dynamic-functions"; +"other/for-sponsors.mdx": { + id: "other/for-sponsors.mdx"; + slug: "other/for-sponsors"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".mdx"] }; -"example/dynamic-themes.mdx": { - id: "example/dynamic-themes.mdx"; - slug: "example/dynamic-themes"; +"other/sponsors.mdx": { + id: "other/sponsors.mdx"; + slug: "other/sponsors"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".mdx"] }; -"example/media-queries.mdx": { - id: "example/media-queries.mdx"; - slug: "example/media-queries"; +"reference/breakpoints.mdx": { + id: "reference/breakpoints.mdx"; + slug: "reference/breakpoints"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".mdx"] }; -"example/variants.mdx": { - id: "example/variants.mdx"; - slug: "example/variants"; +"reference/compound-variants.mdx": { + id: "reference/compound-variants.mdx"; + slug: "reference/compound-variants"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".mdx"] }; -"index.mdx": { - id: "index.mdx"; - slug: "index"; +"reference/create-stylesheet.mdx": { + id: "reference/create-stylesheet.mdx"; + slug: "reference/create-stylesheet"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".mdx"] }; -"reference/create-stylesheet.mdx": { - id: "reference/create-stylesheet.mdx"; - slug: "reference/create-stylesheet"; +"reference/dynamic-functions.mdx": { + id: "reference/dynamic-functions.mdx"; + slug: "reference/dynamic-functions"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; +"reference/errors.mdx": { + id: "reference/errors.mdx"; + slug: "reference/errors"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; +"reference/faq.mdx": { + id: "reference/faq.mdx"; + slug: "reference/faq"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; +"reference/for-library-authors.mdx": { + id: "reference/for-library-authors.mdx"; + slug: "reference/for-library-authors"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; +"reference/media-queries.mdx": { + id: "reference/media-queries.mdx"; + slug: "reference/media-queries"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; +"reference/plugins.mdx": { + id: "reference/plugins.mdx"; + slug: "reference/plugins"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; +"reference/server-side-rendering.mdx": { + id: "reference/server-side-rendering.mdx"; + slug: "reference/server-side-rendering"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; +"reference/theming.mdx": { + id: "reference/theming.mdx"; + slug: "reference/theming"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; +"reference/unistyles-registry.mdx": { + id: "reference/unistyles-registry.mdx"; + slug: "reference/unistyles-registry"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; +"reference/unistyles-runtime.mdx": { + id: "reference/unistyles-runtime.mdx"; + slug: "reference/unistyles-runtime"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; +"reference/use-initial-theme.mdx": { + id: "reference/use-initial-theme.mdx"; + slug: "reference/use-initial-theme"; body: string; collection: "docs"; data: InferEntrySchema<"docs"> @@ -241,6 +311,34 @@ declare module 'astro:content' { collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".mdx"] }; +"reference/variants.mdx": { + id: "reference/variants.mdx"; + slug: "reference/variants"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; +"reference/web-support.mdx": { + id: "reference/web-support.mdx"; + slug: "reference/web-support"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; +"show-case/projects.mdx": { + id: "show-case/projects.mdx"; + slug: "show-case/projects"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; +"show-case/ui-kits.mdx": { + id: "show-case/ui-kits.mdx"; + slug: "show-case/ui-kits"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; "start/basic-usage.mdx": { id: "start/basic-usage.mdx"; slug: "start/basic-usage"; @@ -248,6 +346,27 @@ declare module 'astro:content' { collection: "docs"; data: InferEntrySchema<"docs"> } & { render(): Render[".mdx"] }; +"start/benchmarks.mdx": { + id: "start/benchmarks.mdx"; + slug: "start/benchmarks"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; +"start/introduction.mdx": { + id: "start/introduction.mdx"; + slug: "start/introduction"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; +"start/migration-from-1.mdx": { + id: "start/migration-from-1.mdx"; + slug: "start/migration-from-1"; + body: string; + collection: "docs"; + data: InferEntrySchema<"docs"> +} & { render(): Render[".mdx"] }; "start/migration-from-stylesheet.mdx": { id: "start/migration-from-stylesheet.mdx"; slug: "start/migration-from-stylesheet"; diff --git a/docs/astro.config.mjs b/docs/astro.config.mjs index c839ef7c..6902e381 100644 --- a/docs/astro.config.mjs +++ b/docs/astro.config.mjs @@ -1,59 +1,175 @@ import { defineConfig } from 'astro/config' import starlight from '@astrojs/starlight' import sitemap from '@astrojs/sitemap' +import expressiveCode from 'astro-expressive-code' // https://astro.build/config export default defineConfig({ - integrations: [starlight({ - title: 'Unistyles', - description: "React Native StyleSheet 2.0", - customCss: ['./src/styles/custom.css'], - logo: { - src: './src/assets/logo.svg' - }, - social: { - github: 'https://github.com/jpudysz/react-native-unistyles', - 'x.com': 'https://x.com/jpudysz' - }, - sidebar: [{ - label: 'Start here', - items: [{ - label: 'Setup', - link: '/start/setup/' - }, { - label: 'Basic Usage', - link: '/start/basic-usage/' - }, { - label: 'Migration from StyleSheet', - link: '/start/migration-from-stylesheet/' - }] - }, { - label: 'Reference', - items: [{ - label: 'createStyleSheet', - link: '/reference/create-stylesheet/' - }, { - label: 'useStyles', - link: '/reference/use-styles/' - }] - }, { - label: 'Examples', - items: [{ - label: 'Breakpoints', - link: '/example/breakpoints/' - }, { - label: 'Media queries', - link: '/example/media-queries/' - }, { - label: 'Dynamic functions', - link: '/example/dynamic-functions/' - }, { - label: 'Dynamic themes', - link: '/example/dynamic-themes/' - }, { - label: 'Variants', - link: '/example/variants/' - }] - }] - }), sitemap()] + integrations: [ + starlight({ + title: 'Unistyles', + description: 'React Native StyleSheet 2.0', + customCss: ['./src/styles/custom.css'], + logo: { + src: './src/assets/logo.svg' + }, + social: { + github: 'https://github.com/jpudysz/react-native-unistyles', + 'x.com': 'https://x.com/jpudysz', + discord: 'https://discord.gg/akGHf27P4C' + }, + sidebar: [ + { + label: 'Start here', + items: [ + { + label: 'Introduction', + link: '/start/introduction/' + }, + { + label: 'Setup', + link: '/start/setup/' + }, + { + label: 'Migration from 1.x', + link: '/start/migration-from-1/' + }, + { + label: 'Basic Usage', + link: '/start/basic-usage/' + }, + { + label: 'Migration from StyleSheet', + link: '/start/migration-from-stylesheet/' + }, + { + label: 'Benchmarks', + link: '/start/benchmarks/' + } + ] + }, + { + label: 'Reference', + items: [ + { + label: 'createStyleSheet', + link: '/reference/create-stylesheet/' + }, + { + label: 'useStyles', + link: '/reference/use-styles/' + }, + { + label: 'Dynamic functions', + link: '/reference/dynamic-functions/' + }, + { + label: 'Theming', + link: '/reference/theming/' + }, + { + label: 'useInitialTheme', + link: '/reference/use-initial-theme/' + }, + { + label: 'Breakpoints', + link: '/reference/breakpoints/' + }, + { + label: 'Media queries', + link: '/reference/media-queries/' + }, + { + label: 'Variants', + link: '/reference/variants/' + }, + { + label: 'Compound variants', + link: '/reference/compound-variants/' + }, + { + label: 'Unistyles Registry', + link: '/reference/unistyles-registry/' + }, + { + label: 'Unistyles Runtime', + link: '/reference/unistyles-runtime/' + }, + { + label: 'Plugins', + link: '/reference/plugins/' + }, + { + label: 'Web support', + link: '/reference/web-support/' + }, + { + label: 'Server side rendering', + link: '/reference/server-side-rendering/' + }, + { + label: 'Errors', + link: '/reference/errors/' + }, + { + label: 'FAQ', + link: '/reference/faq/' + } + ] + }, + { + label: 'Show case', + items: [ + { + label: 'Projects', + link: '/show-case/projects/' + }, + { + label: 'UI Kits', + link: '/show-case/ui-kits/' + } + ] + }, + { + label: 'Examples', + items: [ + { + label: 'All examples', + link: '/examples/all' + } + ] + }, + { + label: 'Other', + items: [ + { + label: 'For library authors', + link: '/other/for-library-authors/' + }, + { + label: 'Sponsors', + link: 'other/sponsors/' + }, + { + label: 'For Sponsors', + link: 'other/for-sponsors/' + } + ] + }, + { + label: 'React Native Crossroads', + link: 'https://reactnativecrossroads.com/' + }, + { + label: 'Codemask', + link: 'https://codemask.com/' + } + ] + }), + sitemap(), + expressiveCode({ + theme: 'github-dark-dimmed', + languages: ['typescript', 'tsx'] + }) + ] }) diff --git a/docs/package.json b/docs/package.json index 9eeebec2..c62a0f21 100644 --- a/docs/package.json +++ b/docs/package.json @@ -9,11 +9,21 @@ "astro": "astro" }, "dependencies": { - "@astrojs/sitemap": "3.0.1", - "@astrojs/starlight": "0.11.0", - "astro": "3.2.3", + "@astrojs/sitemap": "3.0.3", + "@astrojs/starlight": "0.15.0", + "@fontsource-variable/nunito": "5.0.17", + "astro": "4.0.2", + "astro-expressive-code": "^0.29.3", "astro-seo": "0.8.0", - "sharp": "0.32.5" + "autoprefixer": "10.4.16", + "motion": "10.16.4", + "postcss-easing-gradients": "3.0.1", + "postcss-nested": "6.0.1", + "sharp": "0.33.0", + "swiper": "11.0.5" }, - "private": true + "private": true, + "devDependencies": { + "@types/swiper": "6.0.0" + } } diff --git a/docs/postcss.config.mjs b/docs/postcss.config.mjs new file mode 100644 index 00000000..571a4e1a --- /dev/null +++ b/docs/postcss.config.mjs @@ -0,0 +1,11 @@ +import autoprefixer from 'autoprefixer' +import postcssNested from 'postcss-nested' +import gradients from 'postcss-easing-gradients' + +export default { + plugins: [ + autoprefixer, + postcssNested, + gradients + ] +} diff --git a/docs/public/features/breakpoints.svg b/docs/public/features/breakpoints.svg new file mode 100644 index 00000000..26c6a10c --- /dev/null +++ b/docs/public/features/breakpoints.svg @@ -0,0 +1 @@ + diff --git a/docs/public/features/bugs.svg b/docs/public/features/bugs.svg new file mode 100644 index 00000000..c086a718 --- /dev/null +++ b/docs/public/features/bugs.svg @@ -0,0 +1 @@ + diff --git a/docs/public/features/cpp.svg b/docs/public/features/cpp.svg new file mode 100644 index 00000000..d94cac27 --- /dev/null +++ b/docs/public/features/cpp.svg @@ -0,0 +1 @@ + diff --git a/docs/public/features/cross-platform.svg b/docs/public/features/cross-platform.svg new file mode 100644 index 00000000..a4e2f503 --- /dev/null +++ b/docs/public/features/cross-platform.svg @@ -0,0 +1 @@ + diff --git a/docs/public/features/fabric.svg b/docs/public/features/fabric.svg new file mode 100644 index 00000000..b829fc89 --- /dev/null +++ b/docs/public/features/fabric.svg @@ -0,0 +1 @@ + diff --git a/docs/public/features/hook.svg b/docs/public/features/hook.svg new file mode 100644 index 00000000..dee01619 --- /dev/null +++ b/docs/public/features/hook.svg @@ -0,0 +1 @@ + diff --git a/docs/public/features/performance.svg b/docs/public/features/performance.svg new file mode 100644 index 00000000..e110353d --- /dev/null +++ b/docs/public/features/performance.svg @@ -0,0 +1 @@ + diff --git a/docs/public/features/plugins.svg b/docs/public/features/plugins.svg new file mode 100644 index 00000000..0683ca19 --- /dev/null +++ b/docs/public/features/plugins.svg @@ -0,0 +1 @@ + diff --git a/docs/public/features/react.svg b/docs/public/features/react.svg new file mode 100644 index 00000000..4f6155ce --- /dev/null +++ b/docs/public/features/react.svg @@ -0,0 +1 @@ + diff --git a/docs/public/features/themes.svg b/docs/public/features/themes.svg new file mode 100644 index 00000000..6d1daf32 --- /dev/null +++ b/docs/public/features/themes.svg @@ -0,0 +1 @@ + diff --git a/docs/public/features/ts.svg b/docs/public/features/ts.svg new file mode 100644 index 00000000..e376a319 --- /dev/null +++ b/docs/public/features/ts.svg @@ -0,0 +1 @@ + diff --git a/docs/public/features/variants.svg b/docs/public/features/variants.svg new file mode 100644 index 00000000..f99be8af --- /dev/null +++ b/docs/public/features/variants.svg @@ -0,0 +1 @@ + diff --git a/docs/public/opengraph-image.jpg b/docs/public/opengraph-image.jpg index 58a1c2ef..2ffb6d04 100644 Binary files a/docs/public/opengraph-image.jpg and b/docs/public/opengraph-image.jpg differ diff --git a/docs/src/assets/arrow-down.svg b/docs/src/assets/arrow-down.svg new file mode 100644 index 00000000..a1a7c979 --- /dev/null +++ b/docs/src/assets/arrow-down.svg @@ -0,0 +1 @@ + diff --git a/docs/src/assets/benchmark-1.png b/docs/src/assets/benchmark-1.png new file mode 100644 index 00000000..e015b1fb Binary files /dev/null and b/docs/src/assets/benchmark-1.png differ diff --git a/docs/src/assets/benchmark-2.png b/docs/src/assets/benchmark-2.png new file mode 100644 index 00000000..8eeaf8a7 Binary files /dev/null and b/docs/src/assets/benchmark-2.png differ diff --git a/docs/src/assets/benchmark-3.png b/docs/src/assets/benchmark-3.png new file mode 100644 index 00000000..85bd593a Binary files /dev/null and b/docs/src/assets/benchmark-3.png differ diff --git a/docs/src/assets/benchmark-4.png b/docs/src/assets/benchmark-4.png new file mode 100644 index 00000000..f9c2adab Binary files /dev/null and b/docs/src/assets/benchmark-4.png differ diff --git a/docs/src/assets/benchmark-5.png b/docs/src/assets/benchmark-5.png new file mode 100644 index 00000000..f54c244c Binary files /dev/null and b/docs/src/assets/benchmark-5.png differ diff --git a/docs/src/assets/cloud-a.svg b/docs/src/assets/cloud-a.svg new file mode 100644 index 00000000..1238b8ec --- /dev/null +++ b/docs/src/assets/cloud-a.svg @@ -0,0 +1 @@ + diff --git a/docs/src/assets/cloud-b.svg b/docs/src/assets/cloud-b.svg new file mode 100644 index 00000000..c6e34e49 --- /dev/null +++ b/docs/src/assets/cloud-b.svg @@ -0,0 +1 @@ + diff --git a/docs/src/assets/codemask-black.svg b/docs/src/assets/codemask-black.svg new file mode 100644 index 00000000..66787597 --- /dev/null +++ b/docs/src/assets/codemask-black.svg @@ -0,0 +1 @@ + diff --git a/docs/src/assets/footer-clouds.svg b/docs/src/assets/footer-clouds.svg new file mode 100644 index 00000000..09b41429 --- /dev/null +++ b/docs/src/assets/footer-clouds.svg @@ -0,0 +1 @@ + diff --git a/docs/src/assets/github.svg b/docs/src/assets/github.svg new file mode 100644 index 00000000..03722aa2 --- /dev/null +++ b/docs/src/assets/github.svg @@ -0,0 +1 @@ + diff --git a/docs/src/assets/hero-mobile.svg b/docs/src/assets/hero-mobile.svg new file mode 100644 index 00000000..580b1c45 --- /dev/null +++ b/docs/src/assets/hero-mobile.svg @@ -0,0 +1 @@ + diff --git a/docs/src/assets/logo.svg b/docs/src/assets/logo.svg index 004a1c19..bbaad45f 100644 --- a/docs/src/assets/logo.svg +++ b/docs/src/assets/logo.svg @@ -1,73 +1 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/docs/src/assets/unicorn.svg b/docs/src/assets/unicorn.svg new file mode 100644 index 00000000..d6110682 --- /dev/null +++ b/docs/src/assets/unicorn.svg @@ -0,0 +1 @@ + diff --git a/docs/src/carousel/index.ts b/docs/src/carousel/index.ts new file mode 100644 index 00000000..5d975d39 --- /dev/null +++ b/docs/src/carousel/index.ts @@ -0,0 +1 @@ +export { init } from './init' diff --git a/docs/src/carousel/init.ts b/docs/src/carousel/init.ts new file mode 100644 index 00000000..bf81a27a --- /dev/null +++ b/docs/src/carousel/init.ts @@ -0,0 +1,25 @@ +import { Swiper } from 'swiper' +import { EffectCoverflow } from 'swiper/modules' + +export const init = (isTablet: boolean) => { + const slidesPerView = isTablet ? 3 : 1.7 + + const swiper = new Swiper('.swiper', { + effect: 'coverflow', + grabCursor: true, + modules: [EffectCoverflow], + centeredSlides: true, + slidesPerView, + loop: true, + spaceBetween: 0, + coverflowEffect: { + rotate: 0, + depth: 1, + scale: 0.8, + modifier: 1, + slideShadows: false + } + }) + + return () => swiper.destroy(true, true) +} diff --git a/docs/src/components/Features.astro b/docs/src/components/Features.astro new file mode 100644 index 00000000..04b719e7 --- /dev/null +++ b/docs/src/components/Features.astro @@ -0,0 +1,164 @@ +--- +import { Image } from 'astro:assets' +import 'swiper/css' +import 'swiper/css/effect-coverflow' + +type Feature = { + icon: string, + description: string +} + +const features = [ + { + description: 'Shared core with C++ and JSI bindings', + icon: 'cpp' + }, + { + description: 'Crazy performance, adds under 0.1 ms to your StyleSheet', + icon: 'performance' + }, + { + description: 'Cross platform support: iOS, Android, Expo, React Native Web, and SSR', + icon: 'cross-platform' + }, + { + description: 'Supports both old and new (Fabric) architecture', + icon: 'fabric' + }, + { + description: 'Use breakpoints and media queries for all screen sizes', + icon: 'breakpoints' + }, + { + description: 'Register multiple themes and change them with single function call', + icon: 'themes' + }, + { + description: 'Keeps your components simple. Everything is packed in one hook', + icon: 'hook' + }, + { + description: 'Battle tested with ~99% test coverage', + icon: 'bugs' + }, + { + description: 'Extend stylesheets with your own plugins', + icon: 'plugins' + }, + { + description: 'Supports user-defined variants', + icon: 'variants' + }, + { + description: 'TypeScript first support, always get perfect Intellisense', + icon: 'ts' + }, + { + description: 'Easy to set up, no need for React Context', + icon: 'react' + } +] satisfies Array +--- + +
+

Features

+
+
+ {features.map((feature) => ( +
+ +

+ {feature.description} +

+
+ ))} +
+
+
+ + + + diff --git a/docs/src/components/Footer.astro b/docs/src/components/Footer.astro new file mode 100644 index 00000000..ac79e101 --- /dev/null +++ b/docs/src/components/Footer.astro @@ -0,0 +1,90 @@ +--- +import { Image } from 'astro:assets' +import CodemaskLogo from '../assets/codemask-black.svg' +import Clouds from '../assets/footer-clouds.svg' + +const motto = 'Bridging Your Idea to the digital world\nYour experts in mobile and web development' +--- + +
+ +
+ + diff --git a/docs/src/components/Hero.astro b/docs/src/components/Hero.astro new file mode 100644 index 00000000..696433c2 --- /dev/null +++ b/docs/src/components/Hero.astro @@ -0,0 +1,300 @@ +--- +import { Image } from 'astro:assets' +import Github from '../assets/github.svg' +import Unicorn from '../assets/unicorn.svg' +import CloudA from '../assets/cloud-a.svg' +import CloudB from '../assets/cloud-b.svg' +import HeroMobile from '../assets/hero-mobile.svg' +import ArrowDown from '../assets/arrow-down.svg' +import MediaImage from './MediaImage.astro' +--- + +
+ + + +
+
+ + + + +
+
+
+

+ Welcome to + Unistyles 2.0 +

+

Level up your React Native StyleSheet!

+ +
+ Click to scroll + +
+
+ + + + diff --git a/docs/src/components/MediaImage.astro b/docs/src/components/MediaImage.astro new file mode 100644 index 00000000..8be4e8cc --- /dev/null +++ b/docs/src/components/MediaImage.astro @@ -0,0 +1,24 @@ +--- +import type { ImageMetadata } from 'astro' + +type MinMax = 'max' | 'min' +type WidthHeight = 'width' | 'height' +type Media = `${MinMax}-${WidthHeight}` +type MediaQuery = `(${Media}: ${number}px)` + +type Props = { + image: ImageMetadata, + class?: string, + mediaQuery: MediaQuery +} + +const { image, mediaQuery } = Astro.props; +--- + + + + + diff --git a/docs/src/components/Seo.astro b/docs/src/components/Seo.astro index 2311e1c0..0419354b 100644 --- a/docs/src/components/Seo.astro +++ b/docs/src/components/Seo.astro @@ -5,7 +5,7 @@ type Props = { seo: { title: string, description?: string, - image: { + image?: { src: string, mimeType: string, alt: string @@ -14,8 +14,8 @@ type Props = { } const { seo: { title, description, image } } = Astro.props as Props -const DEFAULT_TITLE_PAGE = 'Unistyles' -const DEFAULT_DESCRIPTION_PAGE = 'React Native StyleSheet 2.0' +const DEFAULT_TITLE_PAGE = 'Unistyles 2.0' +const DEFAULT_DESCRIPTION_PAGE = 'Level up your React Native StyleSheet!' const DEFAULT_URL_SITE = 'https://reactnativeunistyles.vercel.app' const openGraph = { title: title ||DEFAULT_TITLE_PAGE, diff --git a/docs/src/components/Sponsor.astro b/docs/src/components/Sponsor.astro new file mode 100644 index 00000000..6b0a6bbf --- /dev/null +++ b/docs/src/components/Sponsor.astro @@ -0,0 +1,37 @@ +--- +import type { ImageMetadata } from 'astro' + +type Props = { + url: string, + size: number, + name: string, + link: string, + borderColor: string +} + +const { url, size, name, link, borderColor } = Astro.props; +--- + +
+ + + + {name} + + +
diff --git a/docs/src/components/nav/Hamburger.astro b/docs/src/components/nav/Hamburger.astro new file mode 100644 index 00000000..b3dccd94 --- /dev/null +++ b/docs/src/components/nav/Hamburger.astro @@ -0,0 +1,124 @@ +--- +import { links } from './data' +--- + +
+ +
+
+ +
+ + + + diff --git a/docs/src/components/nav/Nav.astro b/docs/src/components/nav/Nav.astro new file mode 100644 index 00000000..69343c2e --- /dev/null +++ b/docs/src/components/nav/Nav.astro @@ -0,0 +1,88 @@ +--- +import { Image } from 'astro:assets' +import Logo from '../../assets/logo.svg' +import Hamburger from './Hamburger.astro' +import { links } from './data' +--- + + + + diff --git a/docs/src/components/nav/data.ts b/docs/src/components/nav/data.ts new file mode 100644 index 00000000..9e599a77 --- /dev/null +++ b/docs/src/components/nav/data.ts @@ -0,0 +1,14 @@ +export const links = [ + { + name: 'Sponsors', + href: 'other/sponsors/' + }, + { + name: 'Examples', + href: 'examples/all/' + }, + { + name: 'Blog', + href: 'https://www.reactnativecrossroads.com/' + } +] diff --git a/docs/src/content/docs/example/breakpoints.mdx b/docs/src/content/docs/example/breakpoints.mdx deleted file mode 100644 index e4376f40..00000000 --- a/docs/src/content/docs/example/breakpoints.mdx +++ /dev/null @@ -1,62 +0,0 @@ ---- -title: Breakpoints ---- - -import Seo from '../../../components/Seo.astro' - - - Any style can change based on breakpoints. To do this, change a value to an object: - - ```ts - const stylesheet = createStyleSheet(theme => ({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: { - // your breakpoints - xs: theme.colors.background, - sm: theme.colors.barbie - } - }, - text: { - color: theme.colors.typography - } - })) - ``` - - You can even use it with nested objects like `transform` or `shadowOffset`: - - ```ts - const stylesheet = createStyleSheet(theme => ({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - backgroundColor: { - xs: theme.colors.background, - sm: theme.colors.barbie - }, - transform: [ - { - translateX: 100 - }, - { - scale: { - xs: 1.5, - ':w[500]': 1 - } - } - ] - } - })) - ``` - - Library will choose the correct value (based on screen width) in the runtime. - - diff --git a/docs/src/content/docs/example/dynamic-functions.mdx b/docs/src/content/docs/example/dynamic-functions.mdx deleted file mode 100644 index b0f7596e..00000000 --- a/docs/src/content/docs/example/dynamic-functions.mdx +++ /dev/null @@ -1,69 +0,0 @@ ---- -title: Dynamic functions ---- - -import Seo from '../../../components/Seo.astro' - - - Every style can be transformed to dynamic function to take additional parameters from JSX: - - ```tsx - export const ExampleUnistyles = () => { - const { styles } = useStyles(stylesheet) - - return ( - - {posts.map((post, index) => ( - - - {post.title} - - - ))} - - ) - } - - const stylesheet = createStyleSheet({ - scrollContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - // dynamic function - post: (index: number) => ({ - backgroundColor: index % 2 === 0 ? 'gold' : 'silver', - }) - }) - ``` - If you use a dynamic function, library will wrap it in a `Proxy` to make sure the correct values of breakpoints will be used: - - ```ts - const stylesheet = createStyleSheet(theme => ({ - scrollContainer: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - post: (index: number) => ({ - // breakpoints and media queries works with dynamic function - backgroundColor: { - xs: index % 2 === 0 - ? theme.colors.gold - : theme.colors.silver, - sm: theme.colors.red - } - }) - })) -``` - - diff --git a/docs/src/content/docs/example/dynamic-themes.mdx b/docs/src/content/docs/example/dynamic-themes.mdx deleted file mode 100644 index 52087a4c..00000000 --- a/docs/src/content/docs/example/dynamic-themes.mdx +++ /dev/null @@ -1,76 +0,0 @@ ---- -title: Dynamic themes ---- - -import Seo from '../../../components/Seo.astro' - - - -You can incorporate as many themes as you desire in your application. While there's flexibility in how you structure your theme, it's essential to maintain consistency with the TypeScript type: - -To promote reusability and maintainability, it's a good practice to share as many values between themes as possible: - -```ts -// move shared colors to object -const sharedColors = { - barbie: '#ff9ff3', - oak: '#1dd1a1', - sky: '#48dbfb', - fog: '#c8d6e5', - aloes: '#00d2d3' -} - -export const lightTheme = { - colors: { - // reuse or override them - ...sharedColors, - backgroundColor: '#ffffff', - typography: '#000000' - } - // other keys in common with darkTheme -} - -export const darkTheme = { - colors: { - // reuse or override them - ...sharedColors, - backgroundColor: '#000000', - typography: '#ffffff' - } - // other keys in common with lightTheme -} - -// export type that will be used to describe your theme -export type AppTheme = typeof lightTheme | typeof darkTheme -``` - -With the themes set up, modify your `createUnistyles` to consume your `AppTheme` type: - -```ts -export const { useStyles, createStyleSheet } = createUnistyles(breakpoints) -``` - -The final step is to switch your theme based on certain states, persisted values, databases, etc.: - -```tsx -export const App: React.FunctionComponent = () => { - // obtain here your dark or light theme. It can be storage, state, mmkv, or whatever you use - // const [yourAppTheme] = useState(lightTheme) - // const [yourAppTheme] = useYourStorage() - // const [yourAppTheme] = useMMKVObject(Theme) - - // switching theme will re-render your stylesheets automatically - return ( - - - - ) -} -``` - - diff --git a/docs/src/content/docs/example/media-queries.mdx b/docs/src/content/docs/example/media-queries.mdx deleted file mode 100644 index bdabaeda..00000000 --- a/docs/src/content/docs/example/media-queries.mdx +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: Media queries ---- -import Seo from '../../../components/Seo.astro' - - - -For more advanced usage and pixel perfect designs you can also use a custom media queries. Library supports 4 types of media queries (w-width, h-height): - -```ts -:w[200, 500] - with upper and lower bounds, it translates to width from 200-500px -:w[, 800] - with upper bound only, it's equal to width from 0-800px -:h[400] - lower bound only, it means height from 400px -:h[200, 300]:w[500] - combined queries for both width and height -``` - -Media queries can be mixed with breakpoints, but have a bigger priority: - -```tsx -const stylesheet = createStyleSheet(theme => ({ - container: { - justifyContent: 'center', - alignItems: 'center', - flexDirection: { - xs: 'column', - sm: 'row', - }, - backgroundColor: { - md: theme.colors.background, - // even though md might overlap with >600px, lib will use 'barbie' - ':w[600]': theme.colors.barbie - } - }, - text: { - color: theme.colors.typography - } -})) -``` - - diff --git a/docs/src/content/docs/example/variants.mdx b/docs/src/content/docs/example/variants.mdx deleted file mode 100644 index e92bace4..00000000 --- a/docs/src/content/docs/example/variants.mdx +++ /dev/null @@ -1,135 +0,0 @@ ---- -title: Variants ---- - -import Seo from '../../../components/Seo.astro' - - - -`react-native-unistyles` isn't a UI/component library, so you're in charge of designing variants. With no restrictions and using your creativity, you can easily create variants for your components. - -Let's examine variants for the `Text` component. Imagine you want to create several variants for your `Typography` components: -- Heading -- Regular -- Thin - -To achieve this, add variants to your theme: - -```ts -export const lightTheme = { - colors: { - ...sharedColors, - backgroundColor: '#ffffff', - typography: '#000000' - }, - components: { - typography: { - base: { - fontFamily: 'Roboto', - fontSize: 12, - }, - heading: { - fontFamily: 'Roboto-Medium', - fontSize: 24, - }, - regular: { - fontFamily: 'Roboto', - fontSize: 12, - }, - thin: { - fontFamily: 'Roboto-Thin', - fontSize: 12, - }, - bold: { - fontWeight: 'bold' - } - } - } -} -``` -Next, create a base component: - -```tsx -import React from 'react' -import type { PropsWithChildren } from 'react' -import { Text, TextStyle } from 'react-native' -import { createStyleSheet, useStyles } from 'lib/styles' - -interface BaseTextProps extends PropsWithChildren { - bold: boolean, - style: TextStyle -} - -export const BaseText: React.FunctionComponent = ({ - children, - bold = false, - style = {} -}) => { - const {styles} = useStyles(stylesheet) - - return ( - - {children} - - ) -} - -const stylesheet = createStyleSheet(theme => ({ - baseText: { - ...theme.components.typography.base - }, - bold: { - ...theme.components.typography.bold - } -})) -``` -Remember, that if you want to spread styles like so you need to export your theme "as const" for TypeScript. -This is how React Native types works, and you can see the same behavior with StyleSheet.create. - -Now, let's create another variant, e.g., Heading: - -```tsx -import React from 'react' -import type { PropsWithChildren } from 'react' -import { Text, TextStyle } from 'react-native' -import { createStyleSheet, useStyles } from 'lib/styles' -import { BaseText } from 'lib/components' - -interface BaseTextProps extends PropsWithChildren { - bold: boolean, - text: string -} - -export const Heading: React.FunctionComponent = ({ - text, - bold = false -}) => { - const { theme } = useStyles() - - return ( - - {text} - - ) -} -``` -And so on... - - diff --git a/docs/src/content/docs/examples/all.mdx b/docs/src/content/docs/examples/all.mdx new file mode 100644 index 00000000..c9740d32 --- /dev/null +++ b/docs/src/content/docs/examples/all.mdx @@ -0,0 +1,64 @@ +--- +title: Examples +--- + +import Seo from '../../../components/Seo.astro' + + + + +### Explore Examples with Unistyles 2.0 + +:::tip +Delve into a variety of examples that demonstrate the practical applications and capabilities of Unistyles 2.0. +Collection of examples is hosted on GitHub, making it easy for you to browse, learn, and get inspired. +::: + + +#### Theming + +- No registered themes [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/NoThemesScreen.tsx) +- Single theme [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/SingleThemeScreen.tsx) +- Two themes [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/TwoThemesScreen.tsx) +- Multiple themes [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/MultipleThemesScreen.tsx) +- Adaptive mode [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/LightDarkThemesScreen.tsx) +- Adaptive mode with multiple themes [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/MultipleThemesAdaptiveScreen.tsx) + +#### Breakpoints + +- No registered breakpoints [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/NoBreakpointsScreen.tsx) +- With breakpoints [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/WithBreakpointsScreen.tsx) +- With orientation breakpoints [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/OrientationBreakpoints.tsx) + +#### Media queries + +- mq util [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/MediaQueriesWidthHeight.tsx) +- mixed with breakpoints [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examplesMixedMediaQueries.tsx) + +#### Variants + +- Selected variant [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/VariantsScreen.tsx) +- Default variant [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/DefaultVariantScreen.tsx) + +#### Plugins +- Auto guideline plugin [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/AutoGuidelinePluginScreen.tsx) +- High contrast plugin [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/HighContrastPluginScreen.tsx) + +#### UnistylesRuntime + +- All runtime values [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/RuntimeScreen.tsx) +- Runtime in stylesheet [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/RuntimeWithStyleSheetScreen.tsx) + +#### Other +- Memoization [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/MemoizationScreen.tsx) +- PlatformColor [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/PlatformColorsScreen.tsx) +- Compatibility with StyleSheet [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/StyleSheetScreen.tsx) +- useStyles with no arguments [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/NoStyleSheetScreen.tsx) +- Advanced nested stylesheeets with perfect TypeScript support [link](https://github.com/jpudysz/react-native-unistyles/tree/main/examples/expo/src/examples/TypeScriptValidatorTest.tsx) + + diff --git a/docs/src/content/docs/index.mdx b/docs/src/content/docs/index.mdx deleted file mode 100644 index 2c55b4f1..00000000 --- a/docs/src/content/docs/index.mdx +++ /dev/null @@ -1,65 +0,0 @@ ---- -title: Welcome to Unistyles -template: splash -hero: - tagline: Level up your React Native StyleSheet! - image: - file: ../../assets/logo.svg - actions: - - text: Get started - link: /start/setup/ - icon: right-arrow - variant: primary - - text: Examples - link: https://github.com/jpudysz/react-native-unistyles/tree/main/examples - icon: external ---- - -import { Card, CardGrid } from '@astrojs/starlight/components' -import Codemask from '../../components/Codemask.astro' -import Seo from '../../components/Seo.astro' - - - - - ⚡ Blazing fast, adds around ~3ms on top of StyleSheet* - - 🎳 Share up to 100% of your styles across platforms in monorepo - - 🎯 Doesn't introduce new components - - 🖥️ Supports custom breakpoints and css-like media queries - - 🎨 Access theme in your StyleSheets and components - - 🪄 Supports dynamic functions to access values from JSX - - 🥳 Compatible with Expo, Expo Go, Bare React Native and React Native Web - - ⚔️ No 3rd party dependencies - - *-based on this [benchmark](https://github.com/efstathiosntonas/react-native-style-libraries-benchmark) - - - - Read about what drove me to create this library in this blog post [here](https://www.reactnativecrossroads.com/posts/level-up-react-native-styles). - - - - If you found the `react-native-unistyles` time-saving and valuable, please consider sponsoring my work. Your support enables me to continue creating libraries with a fresh approach. - - Github: https://github.com/sponsors/jpudysz - - Ko-fi: https://ko-fi.com/jpudysz - - Your support is greatly appreciated and helps me dedicate more time and resources to creating quality libraries. Thank you for all the support! - - - - diff --git a/docs/src/content/docs/other/for-library-authors.mdx b/docs/src/content/docs/other/for-library-authors.mdx new file mode 100644 index 00000000..c85bce46 --- /dev/null +++ b/docs/src/content/docs/other/for-library-authors.mdx @@ -0,0 +1,119 @@ +--- +title: For library authors +--- + +import Seo from '../../../components/Seo.astro' + + + +Unistyles is highly extensible and can be utilized to build UI Kits and other kinds of projects. + +## Using Unistyles in your library + +`UnistylesRegistry` can be invoked lazily after resolving user-required configurations, such as themes and plugins. + +You can call `UnistylesRegistry` multiple times to override configurations. +However, keep in mind that `UnistylesRegistry` makes a roundtrip to C++, which can add up to **1ms** of additional setup time for Unistyles. + +To manipulate your config without replacing it, you should use [UnistylesRuntime](/reference/unistyles-runtime/). + +## Timing + +Unistyles core does not have an asynchronous API. +This means you can invoke `UnistylesRegistry` right before the first `useStyle` call: + +```tsx +import { UnistylesRegistry, useInitialTheme, useStyles } from 'react-native-unistyles' + +export const App: React.FunctionComponent = () => { + UnistylesRegistry + .addConfig(...) + .addBreakpoints(...) + + useInitialTheme('dark') + + const { styles } = useStyles(stylesheet) + + return ( + // your app + ) +} + +// stylesheet +``` + +:::tip +Registering themes at runtime is also possible, although it is not yet implemented in the core. +If you need this feature, please open a discussion. +::: + +## Plugins + +You can extend Unistyles with your plugins that may transform the output to the desired style. +Currently, Unistyles supports one lifecycle hook: [onParsedStyle](/reference/plugins/). + +## When does Unistyles re-render? + +Unistyles only re-renders when it receives an event from C++. This means `useStyles` supports component memoization. + +You should expect re-renders only when: +- The theme changes. +- The device orientation changes. +- The screen size changes (web only). +- A plugin is registered or unregistered. +- The variant changes. + +:::tip +Storing library config in `UnistylesRuntime` is also a possibility. +If you need this feature, please open a discussion. +::: + +## No React Context + +Unistyles does not use the React Context API. This means that users do not need to wrap their app with a `Provider`, +reducing boilerplate and making your library more user-friendly. + +## New architecture ready + +Unistyles handles all native configurations and supports new architecture, +ensuring you don't need to worry about Fabric or other low-level concepts. + +Your UI Kit can support all React Native apps out of the box. + +## Minimum requirements + +Unistyles is compatible with: +- React Native version >=0.66 +- TypeScript >4.0 +- iOS 11+ +- Android 5+ + +## CSS styles + +There is ongoing work on custom CSS compiler that could +entirely replace the React Native Web StyleSheet API, +offering better Developer Experience (DX) and more predictable re-renders, e.g. with media queries. + +Unistyles 2.0 was developed with this feature in mind. +If you are interested, please open a discussion and share your use case. + +## Why choose Unistyles? + +Unistyles offers a unique architecture not available in any other library. Built on top of the React Native StyleSheet API, it is fully compatible with it. + +Without component abstraction, you have the freedom to build one. Unistyles supports various platforms and is easily extendable, particularly with plugins and variants. + +With its smart architecture, you can maintain the same rendering time as the [core](/start/benchmarks/). + +:::tip +If you need any cool feature to support your UI Kit, please open a [discussion](https://github.com/jpudysz/react-native-unistyles/discussions). + +I'm happy to help you with your use case! +::: + + diff --git a/docs/src/content/docs/other/for-sponsors.mdx b/docs/src/content/docs/other/for-sponsors.mdx new file mode 100644 index 00000000..f4de2952 --- /dev/null +++ b/docs/src/content/docs/other/for-sponsors.mdx @@ -0,0 +1,48 @@ +--- +title: For Sponsors +--- + +import Seo from '../../../components/Seo.astro' + + + +:::tip +Your support can significantly advance the development of Unistyles 2.0 to new heights. We are seeking sponsors to collaborate in creating a more robust, feature-rich library, including the development of a custom compiler. +::: + +### Why sponsor Unistyles? + +- **Advancing Innovation**: Your sponsorship helps in the continuous innovation and improvement of Unistyles. This support is crucial for developing new features and maintaining the library +- **Benefit for Developers and Companies**: Both individual developers and large companies that profit from using Unistyles stand to gain from its enhancements. Your support ensures that Unistyles remains a cutting-edge tool in your development arsenal +- **Limited Free Time Challenge**: The development of innovative libraries like Unistyles is often constrained by the limited free time of creators. Sponsorship can provide the necessary resources for dedicated development time + +### How to sponsor? + +- **Github Sponsorship**: [link](https://github.com/sponsors/jpudysz) +- **Ko-Fi**: [link](https://ko-fi.com/jpudysz) + +### Free options +- **Sharing Unistyles**: A free yet impactful way to support us is by sharing information about Unistyles within your network. Spreading the word helps increase our visibility and user base +- **Twitter Shoutout**: Give us a shoutout on Twitter. Public endorsements and mentions can significantly boost our project's presence and reach + +### Other options + +:::tip[Hire Codemask] +If you're looking to hire a skilled React Native team, Codemask is open for collaboration. We offer expertise and quality in building React Native applications. + +[Contact Codemask](https://codemask.com) +::: + +:::tip[Build Native Library] + If your company is seeking to build a native library, either as an open-source project or a private one, we are open to being hired for such projects. +Our expertise ensures that your project's goals are met with the highest standards. + +[Contace me](https://x.com/jpudysz) +::: + + diff --git a/docs/src/content/docs/other/sponsors.mdx b/docs/src/content/docs/other/sponsors.mdx new file mode 100644 index 00000000..efb7019a --- /dev/null +++ b/docs/src/content/docs/other/sponsors.mdx @@ -0,0 +1,98 @@ +--- +title: Sponsors +--- + +import Seo from '../../../components/Seo.astro' +import Sponsor from '../../../components/Sponsor.astro' +import { Card } from '@astrojs/starlight/components' + + + +:::tip +Do you want to become a sponsor? Read a guide [for sponsors](/other/for-sponsors/). +::: + +### GOLD + +🥇 + +### Silver + +
+ + +
+ +### Bronze + +🥉 + +### Individuals + +
+ +
+ +### Contributors + +
+ + + + + + +
+ +
diff --git a/docs/src/content/docs/reference/breakpoints.mdx b/docs/src/content/docs/reference/breakpoints.mdx new file mode 100644 index 00000000..3489e26e --- /dev/null +++ b/docs/src/content/docs/reference/breakpoints.mdx @@ -0,0 +1,185 @@ +--- +title: Breakpoints +--- + +import Seo from '../../../components/Seo.astro' + + + +Breakpoints are user-defined key/value pairs that describe the boundaries of screen sizes. +There's no limit to the number of breakpoints; you can define as many as you want. + +### Register breakpoints + +To register your breakpoints, create an object with **any** keys: + +```tsx +// breakpoints.ts +export const breakpoints = { + xs: 0, + sm: 576, + md: 768, + lg: 992, + xl: 1200, + superLarge: 2000, + tvLike: 4000 +} as const +``` + +The first breakpoint **must** start with `0`. This is required to simulate CSS cascading, e.g., everything below 576px (`sm` breakoint) +will resolve to `xs` breakpoint. + +:::danger +If you try to register a breakpoint that doesn't start with 0, Unistyles will throw an error: + +"You are trying to register breakpoints that don't start from 0" +::: + +If you use TypeScript you need to override the library's type: + +```tsx /name/ +import { breakpoints } from './breakpoints' + +type AppBreakpoints = typeof breakpoints + +declare module 'react-native-unistyles' { + export interface UnistylesBreakpoints extends AppBreakpoints {} +} +``` + +Finally, to register the breakpoints, call `UnistylesRegistry`: + +```tsx /addBreakpoints/ +import { UnistylesRegistry } from 'react-native-unistyles' +import { breakpoints } from './breakpoints' + +UnistylesRegistry + .addBreakpoints(breakpoints) +``` + +To learn more follow setup [guide](/start/setup/). + +### How to use breakpoints? + +Any style can change based on breakpoints. To do this, change a `value` to an `object`: + +```diff lang="tsx" /xs/ /sm/ del="backgroundColor: theme.colors.background" ins="backgroundColor: {" +const stylesheet = createStyleSheet(theme => ({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: theme.colors.background, + backgroundColor: { ++ // your breakpoints ++ xs: theme.colors.background, ++ sm: theme.colors.barbie ++ } + }, + text: { + color: theme.colors.typography + } +})) +``` + +You can even use it with nested objects like `transform` or `shadowOffset`: + +```ts /xs/ /sm/ /xl/ +const stylesheet = createStyleSheet(theme => ({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: { + xs: theme.colors.background, + sm: theme.colors.barbie + }, + transform: [ + { + translateX: 100 + }, + { + scale: { + xs: 1.5, + xl: 0.9 + } + } + ] + } +})) +``` + +Breakpoints are also avilable with [variants](/reference/variants/). + + +### Built-in breakpoints `landscape` and `portrait` + +Even if you don't use custom breakpoints, you can still utilize Unistyles' predefined breakpoints available on mobile devices: `portrait` and `landscape`. + +- `portrait` will resolve to your device's width in portrait mode +- `landscape` will resolve to your device's width in landscape mode + +:::tip +These breakpoints are only available on mobile unless you register your own. +::: + +```ts /landscape/ /portrait/ +const stylesheet = createStyleSheet(theme => ({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: { + landscape: theme.colors.background, + portrait: theme.colors.barbie + } + } +})) +``` + +### Access the current breakpoint + +You can access the current breakpoint with the `useStyles` hook: + +```tsx +const { breakpoint } = useStyles(stylesheet) +``` + +or with `UnistylesRuntime`: + +```tsx /UnistylesRuntime.breakpoint/ +import { UnistylesRuntime } from 'react-native-unistyles' + +// check the current breakpoint +export const CurrentBreakpoint = () => ( + + Current breakpoint is {UnistylesRuntime.breakpoint} + +) +``` + +### Get registered breakpoints + +Access your registered breakpoints object with `UnsitylesRuntime`: + +```tsx /UnistylesRuntime.breakpoints/ +import { UnistylesRuntime } from 'react-native-unistyles' + +// check the registered breakpoints +export const RegisteredBreakpoints = () => ( + + My registered breakpoint are {JSON.stringify(UnistylesRuntime.breakpoints)} + +) +``` + +:::tip[UnistylesRuntime] +UnistylesRuntime is a powerful feature, and you can learn more about it [here](/reference/unistyles-runtime). +::: + + diff --git a/docs/src/content/docs/reference/compound-variants.mdx b/docs/src/content/docs/reference/compound-variants.mdx new file mode 100644 index 00000000..9fd036ec --- /dev/null +++ b/docs/src/content/docs/reference/compound-variants.mdx @@ -0,0 +1,120 @@ +--- +title: Compound variants +--- + +import Seo from '../../../components/Seo.astro' + + + +Unistyles does not have first-class support for compound variants, but you can easily create them yourself. + +First, you need to define your variants as described in [this guide](/reference/variants). + +```tsx /size/ /color/ +const stylesheet = createStyleSheet({ + container: { + variants: { + size: { + small: { + width: 100, + height: 100 + }, + medium: { + width: 200, + height: 200 + }, + large: { + width: 300, + height: 300 + } + }, + color: { + red: { + backgroundColor: 'red' + }, + green: { + backgroundColor: 'green' + }, + blue: { + backgroundColor: 'blue' + } + } + } + } +}) +``` + +The easiest way to create a compound variant is to use the [dynamic function](/reference/dynamic-functions/). + +```tsx /size/ /color/ +const stylesheet = createStyleSheet({ + container: { + variants: { + size: { + small: { + width: 100, + height: 100 + }, + medium: { + width: 200, + height: 200 + }, + large: { + width: 300, + height: 300 + } + }, + color: { + red: { + backgroundColor: 'red' + }, + green: { + backgroundColor: 'green' + }, + blue: { + backgroundColor: 'blue' + } + } + } + }, + extraStyle = (size, color) => { + if (size === 'small' && color === 'red') { + return { + borderWidth: 1, + borderColor: 'black' + } + } + + if (size === 'medium' && color === 'green') { + return { + borderWidth: 2, + borderColor: 'black' + } + } + + return {} + } +}) +``` + +You can now merge both styles into one component, as with the regular `StyleSheet.create`: + +```tsx /size/ /color/ +const MyComponent = ({ size, color }) => { + const { styles } = useStyles(stylesheet, { + size, + color + }) + + return ( + + ) +} +``` + + diff --git a/docs/src/content/docs/reference/create-stylesheet.mdx b/docs/src/content/docs/reference/create-stylesheet.mdx index a512ec63..df02a5a4 100644 --- a/docs/src/content/docs/reference/create-stylesheet.mdx +++ b/docs/src/content/docs/reference/create-stylesheet.mdx @@ -10,52 +10,56 @@ import Seo from '../../../components/Seo.astro' description: 'How to use createStyleSheet from react-native-unistyles' }} > - `createStyleSheet` is interchangeable with `StyleSheet.create`. You can use objects, and it will function identically to its React Native counterpart. - - ```ts - const stylesheet = createStyleSheet({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - }, - }) - ``` - The difference is that you can now use breakpoints and media queries: - - ```ts - const stylesheet = createStyleSheet({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - flexDirection: { - xs: 'row', - sm: 'column', - ':w[800]': 'row' - } - }, - }) - ``` - - `createStyleSheet` also accepts a function, to which the library will inject your theme: - - ```ts - const stylesheet = createStyleSheet(theme => ({ - container: { - flex: 1, - justifyContent: 'center', - alignItems: 'center', - flexDirection: { - xs: 'row', - sm: 'column', - ':w[800]': 'row' - }, - backgroundColor: theme.colors.background - }, - })) - ``` - - Importantly, you'll receive the same TypeScript hints as with `StyleSheet.create`! + +Utility for building your `StyleSheets` with superpowers. +It can be imported from the `react-native-unistyles`: + +```ts +import { createStyleSheet } from 'react-native-unistyles' +``` + +This tool is interchangeable with React Native's `StyleSheet.create`. + +:::tip[Learn more] +Interested in incrementally migrating from StyleSheet? [Read this guide](/start/migration-from-stylesheet/) +::: + + +`crateStyleSheet` accepts both `object` or `function`. + +### Basic usage (object) + +If you pass an object to the `createStyleSheet` it will work the same like with `StyleSheet.create`. +With this tool you can now use [variants](/reference/variants/), [breakpoints](/reference/breakpoints/) and +[media queries](/reference/media-queries/). + +```ts +const stylesheet = createStyleSheet({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + }, +}) +``` + +### Basic usage (function) + +When you pass a function to `createStyleSheet` it automatically injects a `theme` as the first argument for you. + +To register your themes please follow [setup](/start/setup/) and [theming](/reference/theming/) guides. + +```ts /theme/ +const stylesheet = createStyleSheet(theme => ({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + backgroundColor: theme.colors.background + }, +})) +``` + +Importantly, you'll receive the same TypeScript hints as with `StyleSheet.create`! diff --git a/docs/src/content/docs/reference/dynamic-functions.mdx b/docs/src/content/docs/reference/dynamic-functions.mdx new file mode 100644 index 00000000..c8f86714 --- /dev/null +++ b/docs/src/content/docs/reference/dynamic-functions.mdx @@ -0,0 +1,65 @@ +--- +title: Dynamic functions +--- + +import Seo from '../../../components/Seo.astro' + + + +If you need to pass a value from JSX to your `stylesheet` you can do so using a concept called `dynamic function`. + +### Usage + +To use a dynamic function, change your stylesheet's value from an `object` to a `function`: + +```diff lang="tsx" del="container: {" ins="container: () => ({" +const stylesheet = createStyleSheet(theme => ({ + container: { + container: () => ({ + backgroundColor: theme.colors.background, + flex: 1, + justifyContent: 'center, + alignItems: 'center' +- } ++ }) +})) +``` + +Now, you can pass any number of arguments, all with TypeScript hints: + +```tsx /maxWidth/ /isOdd/ +export const Example = ({ maxWidth, isOdd, children }) => { + const { styles } = useStyles(stylesheet) + + return ( + + {children} + + ) +} + +const stylesheet = createStyleSheet(theme => ({ + container: (maxWidth: number, isOdd: boolean) => ({ + backgroundColor: theme.colors.background, + flex: 1, + justifyContent: 'center, + alignItems: 'center', + maxWidth, + borderBottomWidth: isOdd ? 1 : undefined + }) +})) +``` + +:::caution +It's worth mentioning that while using dynamic functions may be convenient, +you should limit their number to a minimum. There are a few downsides to be aware of: +- On the web, styles created with dynamic functions are always inlined in the `style` attribute +- The `container` style from the example above will recompute the styles every time the component re-renders +::: + + diff --git a/docs/src/content/docs/reference/errors.mdx b/docs/src/content/docs/reference/errors.mdx new file mode 100644 index 00000000..20d89b13 --- /dev/null +++ b/docs/src/content/docs/reference/errors.mdx @@ -0,0 +1,111 @@ +--- +title: Errors +--- + +import Seo from '../../../components/Seo.astro' + + + +Unistyles will only throw errors when it is used incorrectly. + +### RuntimeUnavailable + +:::danger[RuntimeUnavailable] +*"Unistyles runtime is not available. Make sure you followed the installation instructions"* +::: + +This error will be thrown if you try to use Unistyles before it was initialized. +Make sure you followed the installation [setup](/start/setup/). + +Still getting this error? Check [FAQ](/reference/faq/). + +### ThemeNotFound / ThemeNotRegistered + +:::danger[ThemeNotFound / ThemeNotRegistered] +*"You are trying to get a theme that is not registered with UnistylesRegistry"* + +and + +*"You are trying to set a theme that was not registered with UnistylesRegistry"* +::: + + +These errors occur when you call `UnistylesRuntime.setTheme` with a theme that has not been registered with `UnistylesRegistry`. +This situation can arise if you don't use TypeScript. Confirm that all your themes are registered with `UnistylesRegistry.addThemes` before using them. + +### ThemeNotSelected + +:::danger[ThemeNotSelected] +*"Your themes are registered, but you didn't select the initial theme"* +::: + +This error is thrown if you have registered multiple themes but failed to select an initial one. + +Follow theming [guide](/reference/theming/) to learn how to select the initial theme. + +### ThemesCannotBeEmpty + +:::danger[ThemesCannotBeEmpty] +*"You are trying to register empty themes object"* +::: + +This error occurs if you attempt to register an empty themes object with `UnistylesRegistry.addThemes`. + +The `addThemes` method expects a key-value pair with at least one theme. + +Follow theming [guide](/reference/theming/) to learn how to register your themes. + +If you don't want to use themes, simply don't register them. + +### BreakpointsCannotBeEmpty + +:::danger[BreakpointsCannotBeEmpty] +*"You are trying to register empty breakpoints object"* +::: + +This error will be thrown if you try to register empty breakpoints object with `UnistylesRegistry.addBreakpoints`. + +Breakpoints are optional, if you don't want to use them, simply don't register them. + +### BreakpointsMustStartFromZero + +:::danger[BreakpointsMustStartFromZero] +*"You are trying to register breakpoints that don't start from 0"* +::: + +This error occurs when you attempt to register breakpoints that do not start from 0. +Begin your first breakpoint with a value of 0 to mimic CSS cascade. + +### InvalidPluginName + +:::danger[InvalidPluginName] +*"Plugin name can't start from reserved prefix __unistyles"* +::: + +This error is thrown if you attempt to register a plugin with a name that begins with the `__unistyles` prefix. +This prefix is reserved for Unistyles' internal plugins. + +### DuplicatePluginName + +:::danger[DuplicatePluginName] +*"You are trying to register a plugin with a name that is already registered"* +::: + +This error occurs when you attempt to register a plugin with a name that has already been registered. +Review your list of plugins. Perhaps you included the same plugin twice? Or, maybe a plugin with the same name has already been created by someone from the community? + +### CantRemoveInternalPlugin + +:::danger[CantRemoveInternalPlugin] +*"You are trying to remove an internal unistyles plugin"* +::: + +This error is thrown if you attempt to remove an internal Unistyles plugin. +Currently, it's not possible to remove internal plugins as they are essential for Unistyles to function properly. + + diff --git a/docs/src/content/docs/reference/faq.mdx b/docs/src/content/docs/reference/faq.mdx new file mode 100644 index 00000000..759c6697 --- /dev/null +++ b/docs/src/content/docs/reference/faq.mdx @@ -0,0 +1,62 @@ +--- +title: FAQ +--- + +import { Card, CardGrid } from '@astrojs/starlight/components' +import Seo from '../../../components/Seo.astro' + + + +Here, you can find answers to the most frequently asked questions about Unistyles. + +### Why Unistyles runtime is not available? + +This error occurs when you try to use a Unistyles feature without proper installation. + +Possible causes include: +- Running the app in Expo Go, which is not supported. +- Running your Expo project without rebuilding it using the `expo prebuild` command +- Forgetting to execute `pod install` in your bare React Native project +- Omitting the import of the file where you set up your UnistylesRegistry eg. in your `App.tsx` file + +### I'm trying to override library types, but I'm receiving error that react-native-unistyles is not found? + +To override types, you must import something from `react-native-unistyles`, such as `UnistylesRegistry`. + +### Adaptive mode doesn't work for me + +To enable adaptive mode, you need to register two themes named `light` and `dark` and set the `adaptiveThemes` flag to true within `UnistylesRegistry`. + +If your app still doesn't automatically switch themes, ensure that: +- For Expo your `app.json` contains a `userInterfaceStyle` key with the value `automatic` +- For bare React Native, your `Info.plist` does not have the `UIUserInterfaceStyle` key set to a hardcoded value +- `Appearance` from `react-native` is set to `null` +- You have phone with iOS 13+ or Android 10+ +- Your device supports dark mode + +### I'm getting some TypeScript error for my stylesheet + +This should not occur, but if it does, please create a new issue in the GitHub repository. + +Unistyles is built with first-class support for TypeScript, inferring all the types for you. +There should be no need for extra steps. Please include your stylesheet and the error you're encountering in the issue. + +### Does Unistyles support PlatformColor? + +Yes, it does! You can even use PlatformColor in your themes! + +### Are class components supported? + +No, the library only supports functional components. +If you need support for class components, you would need to create a wrapper component. + +### How to use Unistyles with Expo? + +Unistyles supports Expo. However, it can't be used with Expo Go. + + diff --git a/docs/src/content/docs/reference/media-queries.mdx b/docs/src/content/docs/reference/media-queries.mdx new file mode 100644 index 00000000..3def189a --- /dev/null +++ b/docs/src/content/docs/reference/media-queries.mdx @@ -0,0 +1,168 @@ +--- +title: Media Queries +--- + +import Seo from '../../../components/Seo.astro' + + + +Media queries provide more power and allow you to style cross-platform apps with pixel-perfect accuracy. + +### Basic usage + +To use media queries, you need to import the `mq` utility and convert your value to an `object`: + +```diff lang="tsx" /mq/ del="backgroundColor: theme.colors.background," ins="backgroundColor: {" +import { createStyleSheet, mq } from 'react-native-unistyles' + +const stylesheet = createStyleSheet(theme => ({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center' + backgroundColor: theme.colors.background, + backgroundColor: { ++ [mq.only.width(240, 380)]: theme.colors.background, ++ [mq.only.width(380)]: theme.colors.barbie ++ } + } +})) +``` + +The `mq` utility provides Intellisense for quickly building your media queries. + +### Advanced usage + +You can also combine `width` media queries with `height` media queries: + +```tsx /mq/ +import { createStyleSheet, mq } from 'react-native-unistyles' + +const stylesheet = createStyleSheet(theme => ({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center' + backgroundColor: theme.colors.background, + backgroundColor: { + [mq.width(240, 380).and.height(300)]: theme.colors.background, + [mq.width(380).and.height(300)]: theme.colors.barbie + } + } +})) +``` + +Or use only `height` media queries: + +```tsx /mq/ +import { createStyleSheet, mq } from 'react-native-unistyles' + +const stylesheet = createStyleSheet(theme => ({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center' + backgroundColor: theme.colors.background, + backgroundColor: { + [mq.only.height(300, 500)]: theme.colors.background, + [mq.only.height(500)]: theme.colors.barbie + } + } +})) +``` + +You can also reuse your defined [breakpoints](/reference/breakpoints/): + +```tsx /xl/ +import { createStyleSheet, mq } from 'react-native-unistyles' + +const stylesheet = createStyleSheet(theme => ({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center' + backgroundColor: theme.colors.background, + backgroundColor: { + [mq.only.height(500)]: theme.colors.background, + [mq.only.width(200, 'xl')]: theme.colors.barbie + } + } +})) +``` + +### Reference + +```shell title="Available combinations" +mq.only.width // target only width +mq.only.height // target only height +mq.width(...).and.height(...) // target both width and height +mq.height(...).and.width(...) // target both height and width +``` + +```shell title="Available values" +(100, 200) // from 100 to 199 +(400, 'xl') // from 400 to 'xl' breakpoint +('sm', 'md') // from 'sm' to 'md' breakpoint +(undefined, 1000) // from 0 to 999 +(null, 800) // from 0 to 799 +(500) // from 500 onwards +``` + +```shell title="Full example" +mq.only.width(100, 200) // width from 100 to 199 +mq.height(500).and.width('sm') // heigh from 500 onwards and width from 'sm' breakpoint onwards +mq.only.height(null, 1000) // height from 0 to 999 +``` + +:::tip +If you pass an invalid range to mq utility eg. ('xl', 'sm') or (500, 200) the media query will be marked as invalid and won't be used to resolve your styles. +::: + +### Combining media queries with breakpoints + +You can mix media queries with breakpoints, but media queries will always have higher priority: + +```tsx +import { createStyleSheet, mq} from 'react-native-unistyles' + +const stylesheet = createStyleSheet(theme => ({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center' + backgroundColor: { + sm: theme.colors.background, + // Unistyles will firsly resolve to this style, even though sm may be also correct + [mq.only.width(200, 'xl')]: theme.colors.barbie + } + } +})) +``` + +### CSS Media Queries + +CSS Media Queries support is only available on the web. +To use CSS Media Queries set `experimentalCSSMediaQueries` with `UnistylesRegistry`: + +```tsx /experimentalCSSMediaQueries/ +import { UnistylesRegistry } from 'react-native-unistyles' + +UnistylesRegistry + .addConfig({ + experimentalCSSMediaQueries: true + }) +``` + +:::tip +CSS Media queries is an experimental feature and doesn't support all properties yet. +It will be moved to stable in Unistyles 3.0. +::: + +With `experimentalCSSMediaQueries` set to `false`, Unsityles will compute your styles in the JavaScript. + + diff --git a/docs/src/content/docs/reference/plugins.mdx b/docs/src/content/docs/reference/plugins.mdx new file mode 100644 index 00000000..aff2f424 --- /dev/null +++ b/docs/src/content/docs/reference/plugins.mdx @@ -0,0 +1,168 @@ +--- +title: Plugins +--- + +import Seo from '../../../components/Seo.astro' + + + +If you find that Unistyles lacks certain features, you can easily extend its functionality with plugins. + +Plugins are functions that accept a style object and return a new style object. +They are resolved in the order in which they were passed to the `addConfig` function in [UnistylesRegistry](/reference/unistyles-registry). + +### Create a plugin + +To create a plugin, you need to import the `UnistylesPlugin` type: + +```ts +import type { UnistylesPlugin } from 'react-native-unistyles' + +``` + +Then, create a function that conforms to this type: + +```tsx /myPlugin/ +export const myPlugin: UnistylesPlugin = { + name: 'myPlugin', + onParsedStyle: (key, styles, runtime) => { + // parse styles here + } +} +``` + +Your plugin must have a unique name; otherwise, it will be rejected from the registry. + +:::tip +Unistyles uses plugins internally, hence you can't prefix your plugin's name with `__unistyles` prefix. +::: + +### onParsedStyle + +The `onParsedStyle` function is called for every style object in the stylesheet passed to the `useStyles` hook. +These objects are processed **after** Unistyles has finished parsing them, so they do not include: +- variants +- breakpoints +- theme +- media queries + +For example: + +```tsx /container/ /text/ +const stylesheet = createStyleSheet(theme => ({ + container: { + backgroundColor: theme.colors.primary, + padding: 10 + }, + text: { + color: theme.colors.typography, + fontSize: { + sm: 12, + md: 14 + } + } +})) +``` + +You will get, for instance, the following calls: + +```tsx /onParsedStyle/ +onParsedStyle('container', { + backgroundColor: 'pink', + padding: 10 +}, runtime) + +onParsedStyle('text', { + color: '#000000', + fontSize: 12 +}, runtime) +``` + +Your function will be called twice: once for the `container` and once for `text` styles. + +| Argument | Type | Description | +| --- | --- | --- | +| key | string | Name of the style eg. `container` or `text` | +| styles | StyleObject | Style object for the corresponding key | +| runtime | [UnistylesRuntime](/reference/unistyles-runtime/) | Runtime with all required information about your configuration eg. breakpoint, themes etc. | + +### Register plugins with `UnistylesRegistry` + +Once you have your plugin, you can register it with `UnistylesRegistry`: + +```tsx /plugins/ +import { UnistylesRegistry } from 'react-native-unistyles' +import { myPlugin1 } from './myPlugin1' +import { myPlugin2 } from './myPlugin2' + +UnistylesRegistry + .addConfig({ + plugins: [myPlugin1, myPlugin2] + }) +``` + + +### Enable plugin at runtime + +Unistyles offers the option to register plugins at runtime, allowing you to enable them only for specific components. + +```tsx /UnistylesRuntime.addPlugin/ +import { UnistylesRuntime } from 'react-native-unistyles' +import { myPlugin } from './myPlugin' + +export const EnablePlugin: React.FunctionComponent = () => ( +