vybekiit 0.7.25 → 0.7.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin.js +3891 -1301
- package/dist/global-skills/aws-cdk/SKILL.md +19 -5
- package/dist/global-skills/aws-cdk/references/fast-deployments.md +191 -0
- package/dist/global-skills/aws-cdk/references/troubleshooting-deployment.md +16 -0
- package/dist/global-skills/aws-cloudformation/SKILL.md +16 -26
- package/dist/global-skills/aws-cloudformation/references/check-cloudformation-template-compliance.script.md +7 -3
- package/dist/global-skills/aws-cloudformation/references/cloudformation-language-server.md +177 -0
- package/dist/global-skills/aws-cloudformation/references/cloudformation-pre-deploy-validation.script.md +8 -2
- package/dist/global-skills/aws-cloudformation/references/persist-template-context.script.md +5 -8
- package/dist/global-skills/aws-cloudformation/references/retrieve-template-context.script.md +1 -1
- package/dist/global-skills/aws-cloudformation/references/security-considerations.md +51 -0
- package/dist/global-skills/aws-cloudformation/references/troubleshoot-failed-stack.script.md +138 -0
- package/dist/global-skills/aws-cloudformation/references/{validate-cloudformation-template.script.md → validate-with-cfn-lint.script.md} +15 -27
- package/dist/global-skills/aws-cloudformation/references/validate-with-cloudformation-validate.script.md +181 -0
- package/dist/global-skills/aws-cloudformation/references/validation-tool-selection.md +44 -0
- package/dist/global-skills/aws-serverless/SKILL.md +9 -1
- package/dist/global-skills/aws-serverless/references/architecture.md +3 -1
- package/dist/global-skills/aws-serverless/references/lambda.md +3 -1
- package/dist/global-skills/aws-serverless/references/orchestration.md +1 -0
- package/dist/global-skills/better-auth-best-practices/SKILL.md +18 -8
- package/dist/global-skills/eas-app-stores/SKILL.md +31 -15
- package/dist/global-skills/eas-app-stores/agents/openai.yaml +2 -2
- package/dist/global-skills/eas-app-stores/references/ios-app-store.md +37 -32
- package/dist/global-skills/eas-app-stores/references/native-ios.md +167 -0
- package/dist/global-skills/eas-app-stores/references/play-store.md +3 -7
- package/dist/global-skills/eas-app-stores/references/testflight.md +39 -35
- package/dist/global-skills/eas-simulator/SKILL.md +48 -26
- package/dist/global-skills/eas-simulator/references/controllers.md +32 -3
- package/dist/global-skills/eas-simulator/references/run-your-app.md +34 -4
- package/dist/global-skills/eas-simulator/references/troubleshooting.md +8 -4
- package/dist/global-skills/eas-update/SKILL.md +146 -0
- package/dist/global-skills/eas-update/agents/openai.yaml +4 -0
- package/dist/global-skills/expo-animation/RECIPES.md +2 -2
- package/dist/global-skills/expo-animation/SKILL.md +9 -2
- package/dist/global-skills/expo-brownfield/SKILL.md +18 -11
- package/dist/global-skills/expo-brownfield/agents/openai.yaml +2 -2
- package/dist/global-skills/expo-brownfield/references/brownfield-integrated.md +94 -69
- package/dist/global-skills/expo-brownfield/references/brownfield-isolated.md +40 -42
- package/dist/global-skills/expo-brownfield/references/comparison.md +5 -5
- package/dist/global-skills/expo-brownfield/references/feature-integration.md +163 -0
- package/dist/global-skills/expo-brownfield/references/troubleshooting.md +17 -17
- package/dist/global-skills/expo-brownfield/references/version-compatibility.md +40 -0
- package/dist/global-skills/expo-data-fetching/SKILL.md +27 -6
- package/dist/global-skills/expo-design-system/SKILL.md +27 -7
- package/dist/global-skills/expo-design-system/references/audit.md +7 -2
- package/dist/global-skills/expo-design-system/references/native-slop.md +74 -0
- package/dist/global-skills/expo-examples/SKILL.md +0 -1
- package/dist/global-skills/expo-examples/references/catalog.md +1 -1
- package/dist/global-skills/expo-migrate-module/SKILL.md +21 -10
- package/dist/global-skills/expo-migrate-module/references/compatibility.md +80 -23
- package/dist/global-skills/expo-migrate-module/references/migration-map.md +162 -11
- package/dist/global-skills/expo-native-ui/SKILL.md +25 -16
- package/dist/global-skills/expo-native-ui/agents/openai.yaml +2 -2
- package/dist/global-skills/expo-native-ui/references/controls.md +5 -46
- package/dist/global-skills/expo-native-ui/references/icons.md +21 -2
- package/dist/global-skills/expo-native-ui/references/media.md +15 -20
- package/dist/global-skills/expo-native-ui/references/visual-effects.md +12 -11
- package/dist/global-skills/expo-overview/SKILL.md +17 -12
- package/dist/global-skills/expo-router/SKILL.md +5 -3
- package/dist/global-skills/expo-router/references/tabs.md +5 -5
- package/dist/global-skills/expo-skill-eval/scripts/check-static.sh +0 -0
- package/dist/global-skills/expo-skill-eval/scripts/clean-fixture.sh +0 -0
- package/dist/global-skills/expo-skill-eval/scripts/latest-sdk.sh +0 -0
- package/dist/global-skills/expo-skill-eval/scripts/make-fixture.sh +0 -0
- package/dist/global-skills/expo-skill-eval/scripts/make-workspace.sh +0 -0
- package/dist/global-skills/expo-skill-eval/scripts/snapshot-android.sh +0 -0
- package/dist/global-skills/expo-skill-eval/scripts/snapshot-ios.sh +0 -0
- package/dist/global-skills/expo-skill-eval/scripts/snapshot-web.sh +0 -0
- package/dist/global-skills/expo-upgrade/SKILL.md +3 -1
- package/dist/global-skills/expo-web-to-native/references/false-friends.md +2 -2
- package/dist/global-skills/expo-web-to-native/references/native-patterns.md +1 -1
- package/dist/global-skills/firebase-ai-logic-basics/SKILL.md +13 -16
- package/dist/global-skills/firebase-ai-logic-basics/references/ios_setup.md +4 -5
- package/dist/global-skills/firebase-ai-logic-basics/references/usage_patterns_android.md +4 -4
- package/dist/global-skills/firebase-ai-logic-basics/references/usage_patterns_web.md +3 -3
- package/dist/global-skills/firebase-auth-basics/SKILL.md +11 -6
- package/dist/global-skills/firebase-auth-basics/references/client_sdk_android.md +4 -5
- package/dist/global-skills/firebase-auth-basics/references/client_sdk_web.md +3 -3
- package/dist/global-skills/firebase-auth-basics/references/flutter_setup.md +24 -25
- package/dist/global-skills/firebase-auth-basics/references/security_rules.md +4 -2
- package/dist/global-skills/firebase-crashlytics/references/android_setup.md +7 -4
- package/dist/global-skills/firebase-crashlytics/references/ios_setup.md +2 -3
- package/dist/global-skills/firebase-data-connect/SKILL.md +2 -1
- package/dist/global-skills/firebase-data-connect/examples.md +4 -4
- package/dist/global-skills/firebase-data-connect/reference/config.md +5 -4
- package/dist/global-skills/firebase-data-connect/reference/realtime.md +1 -2
- package/dist/global-skills/firebase-data-connect/reference/sdk_flutter.md +2 -2
- package/dist/global-skills/firebase-data-connect/reference/sdk_ios.md +2 -2
- package/dist/global-skills/firebase-data-connect/reference/sdk_web.md +17 -6
- package/dist/global-skills/firebase-data-connect/reference/security.md +5 -5
- package/dist/global-skills/firebase-data-connect/templates.md +2 -1
- package/dist/global-skills/firebase-firestore/SKILL.md +20 -8
- package/dist/global-skills/firebase-firestore/references/enterprise/android_sdk_usage.md +5 -4
- package/dist/global-skills/firebase-firestore/references/enterprise/data_model.md +12 -3
- package/dist/global-skills/firebase-firestore/references/enterprise/indexes.md +16 -18
- package/dist/global-skills/firebase-firestore/references/enterprise/provisioning.md +1 -1
- package/dist/global-skills/firebase-firestore/references/enterprise/python_sdk_usage.md +5 -1
- package/dist/global-skills/firebase-firestore/references/enterprise/web_sdk_usage.md +7 -7
- package/dist/global-skills/firebase-firestore/references/standard/android_sdk_usage.md +5 -5
- package/dist/global-skills/firebase-firestore/references/standard/flutter_setup.md +4 -4
- package/dist/global-skills/firebase-firestore/references/standard/indexes.md +16 -18
- package/dist/global-skills/firebase-firestore/references/standard/provisioning.md +1 -1
- package/dist/global-skills/firebase-remote-config-basics/SKILL.md +0 -5
- package/dist/global-skills/firebase-remote-config-basics/references/android_setup.md +36 -8
- package/dist/global-skills/firebase-remote-config-basics/references/ios_setup.md +1 -7
- package/dist/global-skills/firebase-security-rules-auditor/SKILL.md +17 -6
- package/dist/global-skills/{firebase-firestore/references/standard/security_rules.md → firestore-rules-creation/SKILL.md} +24 -13
- package/dist/global-skills/grow-my-customers/SKILL.md +23 -0
- package/dist/global-skills/instrument-feature-flags/SKILL.md +25 -25
- package/dist/global-skills/instrument-feature-flags/references/adding-feature-flag-code.md +141 -285
- package/dist/global-skills/instrument-feature-flags/references/android.md +6 -15
- package/dist/global-skills/instrument-feature-flags/references/api.md +4 -11
- package/dist/global-skills/instrument-feature-flags/references/best-practices.md +1 -13
- package/dist/global-skills/instrument-feature-flags/references/django.md +14 -27
- package/dist/global-skills/instrument-feature-flags/references/dotnet.md +20 -79
- package/dist/global-skills/instrument-feature-flags/references/elixir.md +1 -9
- package/dist/global-skills/instrument-feature-flags/references/flask.md +13 -13
- package/dist/global-skills/instrument-feature-flags/references/flutter.md +3 -24
- package/dist/global-skills/instrument-feature-flags/references/go.md +3 -15
- package/dist/global-skills/instrument-feature-flags/references/ios.md +4 -17
- package/dist/global-skills/instrument-feature-flags/references/java.md +5 -13
- package/dist/global-skills/instrument-feature-flags/references/laravel.md +13 -17
- package/dist/global-skills/instrument-feature-flags/references/next-js.md +25 -32
- package/dist/global-skills/instrument-feature-flags/references/nodejs.md +8 -15
- package/dist/global-skills/instrument-feature-flags/references/php.md +1 -15
- package/dist/global-skills/instrument-feature-flags/references/python.md +2 -15
- package/dist/global-skills/instrument-feature-flags/references/react-native.md +13 -15
- package/dist/global-skills/instrument-feature-flags/references/react.md +17 -21
- package/dist/global-skills/instrument-feature-flags/references/ruby-on-rails.md +37 -83
- package/dist/global-skills/instrument-feature-flags/references/ruby.md +2 -15
- package/dist/global-skills/instrument-feature-flags/references/rust.md +13 -25
- package/dist/global-skills/instrument-feature-flags/references/usage.md +14 -63
- package/dist/global-skills/instrument-feature-flags/references/web.md +9 -14
- package/dist/global-skills/instrument-product-analytics/SKILL.md +29 -29
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-hybrid.md +3 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-ssr.md +3 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-static.md +3 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-astro-view-transitions.md +3 -1
- package/dist/global-skills/instrument-product-analytics/references/EXAMPLE-ruby-on-rails.md +3 -1
- package/dist/global-skills/instrument-product-analytics/references/android.md +72 -107
- package/dist/global-skills/instrument-product-analytics/references/angular.md +26 -28
- package/dist/global-skills/instrument-product-analytics/references/astro.md +13 -24
- package/dist/global-skills/instrument-product-analytics/references/configuration.md +45 -63
- package/dist/global-skills/instrument-product-analytics/references/django.md +14 -27
- package/dist/global-skills/instrument-product-analytics/references/dotnet.md +20 -79
- package/dist/global-skills/instrument-product-analytics/references/elixir.md +47 -49
- package/dist/global-skills/instrument-product-analytics/references/flask.md +13 -13
- package/dist/global-skills/instrument-product-analytics/references/flutter.md +60 -90
- package/dist/global-skills/instrument-product-analytics/references/go.md +17 -56
- package/dist/global-skills/instrument-product-analytics/references/identify-users.md +15 -15
- package/dist/global-skills/instrument-product-analytics/references/ios.md +11 -15
- package/dist/global-skills/instrument-product-analytics/references/laravel.md +13 -17
- package/dist/global-skills/instrument-product-analytics/references/next-js.md +25 -32
- package/dist/global-skills/instrument-product-analytics/references/nuxt-js-3-6.md +13 -27
- package/dist/global-skills/instrument-product-analytics/references/nuxt-js.md +14 -28
- package/dist/global-skills/instrument-product-analytics/references/php.md +33 -84
- package/dist/global-skills/instrument-product-analytics/references/posthog-python.md +229 -9
- package/dist/global-skills/instrument-product-analytics/references/python.md +415 -106
- package/dist/global-skills/instrument-product-analytics/references/react-native.md +161 -155
- package/dist/global-skills/instrument-product-analytics/references/react-router-v6.md +12 -33
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-data-mode.md +15 -33
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-declarative-mode.md +12 -33
- package/dist/global-skills/instrument-product-analytics/references/react-router-v7-framework-mode.md +26 -41
- package/dist/global-skills/instrument-product-analytics/references/ruby-on-rails.md +37 -83
- package/dist/global-skills/instrument-product-analytics/references/ruby.md +48 -108
- package/dist/global-skills/instrument-product-analytics/references/svelte.md +18 -24
- package/dist/global-skills/instrument-product-analytics/references/tanstack-start.md +17 -19
- package/dist/global-skills/instrument-product-analytics/references/usage.md +14 -63
- package/dist/global-skills/instrument-product-analytics/references/vue-js.md +29 -28
- package/dist/global-skills/manifest.json +8 -2
- package/dist/global-skills/mongodb-search-and-ai/SKILL.md +28 -37
- package/dist/global-skills/mongodb-search-and-ai/references/automated-embedding.md +438 -0
- package/dist/global-skills/mongodb-search-and-ai/references/hybrid-search.md +60 -4
- package/dist/global-skills/mongodb-search-and-ai/references/vector-search.md +46 -108
- package/dist/global-skills/neon/SKILL.md +207 -213
- package/dist/global-skills/neon/references/auth.md +12 -0
- package/dist/global-skills/neon/references/claimable-neon.md +10 -14
- package/dist/global-skills/neon/references/function-triggers.md +53 -0
- package/dist/global-skills/neon/references/logs-loki.md +61 -0
- package/dist/global-skills/neon/references/parse-env.md +32 -0
- package/dist/global-skills/neon/references/sdk.md +7 -0
- package/dist/global-skills/neon-ai-gateway/SKILL.md +14 -16
- package/dist/global-skills/neon-auth/SKILL.md +155 -0
- package/dist/global-skills/neon-auth/references/managed-auth.md +173 -0
- package/dist/global-skills/neon-auth/references/self-managed.md +25 -0
- package/dist/global-skills/neon-functions/SKILL.md +159 -84
- package/dist/global-skills/neon-functions/references/ai-sdk.md +4 -6
- package/dist/global-skills/neon-functions/references/function-triggers.md +249 -0
- package/dist/global-skills/neon-functions/references/mastra-studio.md +3 -3
- package/dist/global-skills/neon-functions/references/mcp.md +1 -1
- package/dist/global-skills/neon-functions/references/production-hardening.md +340 -0
- package/dist/global-skills/neon-functions/references/sse.md +8 -5
- package/dist/global-skills/neon-object-storage/SKILL.md +10 -11
- package/dist/global-skills/neon-postgres/SKILL.md +120 -17
- package/dist/global-skills/neon-postgres/references/full-text-search.md +99 -0
- package/dist/global-skills/neon-postgres/references/hybrid-search.md +90 -0
- package/dist/global-skills/neon-postgres/references/lakebase-search-drizzle.md +172 -0
- package/dist/global-skills/neon-postgres/references/vector-search.md +137 -0
- package/dist/global-skills/neon-postgres-branches/SKILL.md +3 -3
- package/dist/global-skills/neon-postgres-egress-optimizer/SKILL.md +1 -1
- package/dist/global-skills/onboarding/SKILL.md +8 -6
- package/dist/global-skills/resend/SKILL.md +4 -2
- package/dist/global-skills/resend/references/broadcasts.md +6 -1
- package/dist/global-skills/resend/references/receiving.md +29 -10
- package/dist/global-skills/resend/references/sending/email-management.md +14 -4
- package/dist/global-skills/resend/references/topics.md +9 -6
- package/dist/global-skills/resend/references/usage.md +117 -0
- package/dist/global-skills/resend/references/webhooks.md +59 -2
- package/dist/global-skills/stripe-best-practices/SKILL.md +35 -29
- package/dist/global-skills/stripe-best-practices/references/billing.md +9 -2
- package/dist/global-skills/stripe-best-practices/references/payments.md +4 -2
- package/dist/global-skills/stripe-best-practices/references/security.md +3 -1
- package/dist/global-skills/stripe-best-practices/references/tax.md +39 -20
- package/dist/global-skills/supabase/SKILL.md +6 -0
- package/dist/global-skills/use-railway/SKILL.md +42 -22
- package/dist/global-skills/use-railway/references/analyze-db.md +7 -6
- package/dist/global-skills/use-railway/references/cloud-agents.md +70 -0
- package/dist/global-skills/use-railway/references/configure.md +17 -2
- package/dist/global-skills/use-railway/references/databases.md +107 -0
- package/dist/global-skills/use-railway/references/deploy.md +5 -5
- package/dist/global-skills/use-railway/references/feature-flags.md +25 -13
- package/dist/global-skills/use-railway/references/iac.md +66 -77
- package/dist/global-skills/use-railway/references/operate.md +26 -3
- package/dist/global-skills/use-railway/references/request.md +31 -23
- package/dist/global-skills/use-railway/references/setup.md +16 -5
- package/dist/global-skills/use-railway/references/tracing.md +261 -0
- package/dist/global-skills/use-railway/references/usage.md +52 -0
- package/dist/global-skills/use-railway/scripts/analyze-mongo.py +0 -0
- package/dist/global-skills/use-railway/scripts/analyze-mysql.py +0 -0
- package/dist/global-skills/use-railway/scripts/analyze-postgres.py +0 -0
- package/dist/global-skills/use-railway/scripts/analyze-redis.py +0 -0
- package/dist/global-skills/use-railway/scripts/enable-pg-stats.py +0 -0
- package/dist/global-skills/use-railway/scripts/pg-extensions.py +0 -0
- package/dist/global-skills/use-railway/scripts/railway-api.sh +0 -0
- package/dist/global-skills/validate-my-idea/SKILL.md +54 -0
- package/dist/global-skills/{feedback → vybekiit-feedback}/SKILL.md +16 -12
- package/dist/global-skills/watch-my-app/SKILL.md +53 -0
- package/dist/global-skills/workers-best-practices/SKILL.md +36 -103
- package/dist/global-skills/workers-best-practices/references/configuration.md +139 -0
- package/dist/global-skills/workers-best-practices/references/platform-apis.md +51 -0
- package/dist/global-skills/workers-best-practices/references/{rules.md → runtime-patterns.md} +13 -137
- package/dist/global-skills/wrangler/SKILL.md +48 -901
- package/dist/global-skills/xcode-project-setup/scripts/xcode_spm_setup/Sources/main.swift +19 -15
- package/package.json +22 -22
- package/dist/global-skills/expo-native-ui/references/animations.md +0 -220
- package/dist/global-skills/firebase-firestore/references/enterprise/security_rules.md +0 -577
- package/dist/global-skills/workers-best-practices/references/review.md +0 -174
|
@@ -19,7 +19,7 @@ public final class CameraModule: Module {
|
|
|
19
19
|
public final class CameraModule: Module {}
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
Always carry a custom 1.0 name into `@ExpoModule("...")`. A stale `Name(...)` can override or conflict with the 2.0 name depending on the core revision. In mixed mode, remove `Name(...)` only after verifying the
|
|
22
|
+
Always carry a custom 1.0 name into `@ExpoModule("...")`. A stale `Name(...)` can override or conflict with the 2.0 name depending on the core revision. In mixed mode, remove `Name(...)` only after verifying that the module still resolves under its expected JS name.
|
|
23
23
|
|
|
24
24
|
## Functions
|
|
25
25
|
|
|
@@ -108,9 +108,56 @@ func download(url: URL) -> Task<DownloadResult, any Error> {
|
|
|
108
108
|
|
|
109
109
|
This shape requires the `JavaScriptEncodable` conformance for `Task` in the checked-out core (encode-only; a JS promise does not decode back into a `Task`). Verify it exists before using this shape; prefer shape 1 or 2 when it is absent.
|
|
110
110
|
|
|
111
|
-
|
|
111
|
+
#### Threading: the architectural difference
|
|
112
112
|
|
|
113
|
-
|
|
113
|
+
This is the highest-risk part of an async migration, because the code compiles and the JS signature is unchanged while the work moves onto a different thread.
|
|
114
|
+
|
|
115
|
+
The two versions schedule async work from opposite starting points:
|
|
116
|
+
|
|
117
|
+
| | 1.0 `AsyncFunction` | 2.0 `async` `@JS` |
|
|
118
|
+
| --- | --- | --- |
|
|
119
|
+
| Where the body starts | off the JS thread, from the first statement | on the JS thread |
|
|
120
|
+
| When it leaves the JS thread | never runs there | at the first real suspension point |
|
|
121
|
+
| A body with no `await` | still runs off the JS thread | runs entirely on the JS thread |
|
|
122
|
+
|
|
123
|
+
A 2.0 async member is `@JavaScriptActor`-isolated and stays on the JS thread until it actually suspends. So the dangerous case is a function marked `async` whose body never awaits anything, or awaits only after doing substantial work. In 1.0 that body was off the JS thread from the start; migrated verbatim to 2.0, it now blocks the JS thread for its full duration. Nothing in the JS contract reveals this: the function still returns a promise.
|
|
124
|
+
|
|
125
|
+
Audit every `AsyncFunction` you migrate for what the body does before its first `await`:
|
|
126
|
+
|
|
127
|
+
- No `await` at all, or synchronous work ahead of the first `await`: the work is on the JS thread. Treat it as a regression unless the body is trivial.
|
|
128
|
+
- Blocking I/O, file or database access, image or crypto work, or an unbounded loop: never leave this on the JS actor.
|
|
129
|
+
- `.runOnQueue(...)` in 1.0: the queue was the contract. See the continuation pattern below.
|
|
130
|
+
|
|
131
|
+
To restore the 1.0 behavior, use the `@JS` macro's `.concurrent` option (macros plugin `0.10.0`):
|
|
132
|
+
|
|
133
|
+
```swift
|
|
134
|
+
// Body runs off the JS thread, like a 1.0 AsyncFunction.
|
|
135
|
+
@JS(.concurrent)
|
|
136
|
+
func process(input: String) async throws -> String {
|
|
137
|
+
return try self.processor.process(input)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// The option is variadic, so it combines with an explicit JS name.
|
|
141
|
+
@JS("doWork", .concurrent)
|
|
142
|
+
func performWork() async throws {}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Only the body moves. Arguments are still decoded on the JS thread and the result is still encoded back on it, so `.concurrent` changes execution, not the JS contract. This makes it the preferred fix for a migrated function that relied on 1.0 background execution: the intent is explicit and the body stays as written.
|
|
146
|
+
|
|
147
|
+
Two constraints:
|
|
148
|
+
|
|
149
|
+
- `.concurrent` is valid only on an `async` function. On anything else it is diagnosed on the `@JS` attribute.
|
|
150
|
+
- You must still write `async` yourself. The macro cannot add it, but the diagnostic for a synchronous function carries a fix-it that inserts it.
|
|
151
|
+
|
|
152
|
+
**Check availability first.** The plugin side shipped in `0.10.0`, but this also needs a `JSOptions` type and a second `@JS` overload in core, because the options-only spelling cannot reuse the unlabeled JS-name slot. Core has lagged the plugin on every 2.0 feature so far, and where it still declares `@JS(_ jsName: String? = nil)` alone, `@JS(.concurrent)` does not compile. Read the checked-out declaration:
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
grep -rn 'public macro JS' <expo-modules-core>
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
If it takes only a name, use the explicit patterns below instead.
|
|
159
|
+
|
|
160
|
+
For a queue-pinned function, or when `.concurrent` is unavailable, prefer restructuring the work onto the Swift Concurrency model (structured concurrency, an actor, or a detached task for blocking work). When that is not feasible because the queue itself is the contract, for example a library that must be called from one serial queue, convert to an `async` method that dispatches to that queue inside a checked continuation:
|
|
114
161
|
|
|
115
162
|
```swift
|
|
116
163
|
// 1.0
|
|
@@ -217,10 +264,6 @@ final class Download: SharedObject {
|
|
|
217
264
|
|
|
218
265
|
@JS
|
|
219
266
|
var progress: Double { currentProgress }
|
|
220
|
-
|
|
221
|
-
// Installs on the JS class (constructor) object, not the prototype.
|
|
222
|
-
@JS
|
|
223
|
-
static let maxConcurrent = 4
|
|
224
267
|
}
|
|
225
268
|
|
|
226
269
|
@ExpoModule(classes: [Download.self])
|
|
@@ -229,10 +272,62 @@ final class DownloadModule: Module {}
|
|
|
229
272
|
|
|
230
273
|
Drop the leading owner argument used by instance DSL closures; use `self` in the real instance method. Preserve constructor arity and JS member names.
|
|
231
274
|
|
|
232
|
-
Migrate only when the checked-out core supplies the shared-object decoration and construction hooks.
|
|
275
|
+
Migrate only when the checked-out core supplies the shared-object decoration and construction hooks. Never create both a 1.0 `Class(...)` entry and a 2.0 registration for the same class without confirming that core intentionally merges them.
|
|
233
276
|
|
|
234
277
|
Static members belong to shared objects, not modules: a module is exported to JS as an instance, so Swift `static` members on a `Module` class are not useful there. Keep module-level values as instance `@JS` members.
|
|
235
278
|
|
|
279
|
+
#### Static members
|
|
280
|
+
|
|
281
|
+
A static member installs on the JS class object itself, so JS reaches it as `Download.supportedSchemes()`, not through an instance. The 1.0 DSL has dedicated components for this: `StaticFunction` and `StaticAsyncFunction`. Unlike `Function`, they do not receive the instance as their first argument. There is no static *property* component in 1.0, so a static value was exposed as a `StaticFunction` returning it.
|
|
282
|
+
|
|
283
|
+
```swift
|
|
284
|
+
// 1.0
|
|
285
|
+
Class(Download.self) {
|
|
286
|
+
Constructor { (url: URL) in Download(url: url) }
|
|
287
|
+
|
|
288
|
+
// Instance function: receives the owner as its first argument.
|
|
289
|
+
Function("pause") { (download: Download) in
|
|
290
|
+
download.pause()
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Static: no owner argument, callable as Download.supportedSchemes()
|
|
294
|
+
StaticFunction("supportedSchemes") { () -> [String] in
|
|
295
|
+
Download.supportedSchemes
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
```swift
|
|
301
|
+
// 2.0
|
|
302
|
+
@SharedObject
|
|
303
|
+
final class Download: SharedObject {
|
|
304
|
+
@JS
|
|
305
|
+
init(url: URL) { self.url = url }
|
|
306
|
+
|
|
307
|
+
@JS
|
|
308
|
+
func pause() {}
|
|
309
|
+
|
|
310
|
+
@JS
|
|
311
|
+
static func supportedSchemes() -> [String] {
|
|
312
|
+
return Self.schemes
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
`StaticFunction` and `StaticAsyncFunction` both collapse into a `static` (or `class`) Swift declaration, with `async` carrying the distinction, exactly as `Function`/`AsyncFunction` do for instance members. Because 1.0 had no static property component, a `StaticFunction` that only returns a stored value can become a `@JS static var` in 2.0. That changes the JS contract from a call to a property access, so do it only when the user asks; a syntax migration keeps it a function.
|
|
318
|
+
|
|
319
|
+
What decides whether any of this works is the binding path: instance members bind onto the class prototype, while `static`/`class` members bind onto the constructor object through a separate, `constructor:`-labeled decoration hook. The two are independent capabilities.
|
|
320
|
+
|
|
321
|
+
**Support status.** The macros plugin has emitted the `constructor:` binding since `0.7.0`, but core shipped the `prototype:` overload well ahead of it. Where only `prototype:` is declared, a `@JS static` member expands and then fails to link. Verify before migrating any static member:
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
grep -rn '_decorateSharedObject' <expo-modules-core>
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
If only the `prototype:` overload is declared, keep every `StaticFunction`/`StaticAsyncFunction` entry in the 1.0 `Class(...)` block. Mixed mode is the expected outcome here: migrate the constructor and instance members, leave the static entries on the DSL, and say in the handoff that static members stayed behind because the installed core does not bind them yet.
|
|
328
|
+
|
|
329
|
+
Static functions and static properties travel this same path, so neither is available ahead of the other. When the hook is present, confirm it by calling the member on the JS class rather than on an instance; an instance-side test passes whether or not static binding works.
|
|
330
|
+
|
|
236
331
|
## Records
|
|
237
332
|
|
|
238
333
|
Attach `@Record`, remove field wrappers, and encode requiredness in the declaration:
|
|
@@ -256,11 +351,67 @@ Preserve the 1.0 contract exactly. For example, migrate `@Field var source: URL?
|
|
|
256
351
|
|
|
257
352
|
Every stored property is part of the 2.0 record surface. If the old type contains stored bookkeeping that was not a 1.0 field, move it out of the record or leave the type on 1.0; there is no field opt-out. Verify that each field supports the required `JavaScriptDecodable`/`JavaScriptEncodable` direction.
|
|
258
353
|
|
|
259
|
-
|
|
354
|
+
## Unions
|
|
355
|
+
|
|
356
|
+
`@Union` (macros plugin `0.10.0`) is the typed, N-case alternative to 1.0's `Either` types. It applies to a non-generic `enum` whose every case carries exactly one associated value:
|
|
357
|
+
|
|
358
|
+
```swift
|
|
359
|
+
// 1.0: JS `string | SourceOptions`, unwrapped by probing each side.
|
|
360
|
+
@JS
|
|
361
|
+
func load(_ source: Either<String, SourceOptions>) throws {
|
|
362
|
+
if let text: String = source.get() {
|
|
363
|
+
load(url: URL(string: text))
|
|
364
|
+
} else if let options: SourceOptions = source.get() {
|
|
365
|
+
load(url: options.url, headers: options.headers)
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
```swift
|
|
371
|
+
// 2.0: the same JS type, as a named enum.
|
|
372
|
+
@Union
|
|
373
|
+
enum Source {
|
|
374
|
+
case text(String)
|
|
375
|
+
case options(SourceOptions) // a @Record
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
@JS
|
|
379
|
+
func load(_ source: Source) throws {
|
|
380
|
+
switch source { // exhaustive; each payload keeps its static type
|
|
381
|
+
case .text(let text):
|
|
382
|
+
load(url: URL(string: text))
|
|
383
|
+
case .options(let options):
|
|
384
|
+
load(url: options.url, headers: options.headers)
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
```
|
|
388
|
+
|
|
389
|
+
The JS-visible type is unchanged: case order defines the accepted alternatives, so `Either<String, SourceOptions>` maps to cases in that same order. Decoding tries each case's payload in declaration order and the first match wins, which matters for overlapping types (`Int`/`Double`, `URL`/`String`): keep the 1.0 `Either` order, since reordering silently changes which case a given JS value lands in.
|
|
390
|
+
|
|
391
|
+
Besides `switch`, a value unwraps by payload type without naming the case, which is the closest analogue to 1.0's `get()`:
|
|
392
|
+
|
|
393
|
+
```swift
|
|
394
|
+
let text = try source.as(String.self) // throws on a mismatch
|
|
395
|
+
let options = try? source.as(SourceOptions.self) // SourceOptions?
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
A type the union does not carry is a compile error, not a runtime `nil`. A mismatch on a held value throws, where 1.0's `get()` returned `nil`, so a migrated probe chain must become a `switch` or a `try?`.
|
|
399
|
+
|
|
400
|
+
Constraints, each a compile error: a generic enum, no cases, a case with no payload or more than one, a default value on the payload, and two cases with the identical payload spelling.
|
|
401
|
+
|
|
402
|
+
**Check availability first.** The plugin side shipped in `0.10.0`, but this also needs the `Union` macro declaration and the `UnionCaseMismatch` exception in core:
|
|
403
|
+
|
|
404
|
+
```bash
|
|
405
|
+
grep -rn 'public macro Union\|UnionCaseMismatch' <expo-modules-core>
|
|
406
|
+
```
|
|
407
|
+
|
|
408
|
+
Keep `Either` types on the 1.0 DSL until both exist in the target. Treat adopting `@Union` as an API-shape change to raise with the user rather than a mechanical step: it renames nothing in JS, but it does restructure Swift call sites.
|
|
260
409
|
|
|
261
410
|
## Views and lifecycle
|
|
262
411
|
|
|
263
|
-
Keep UIKit `View`, `Prop`, view `Events`, and `OnViewDidUpdateProps` DSL entries
|
|
412
|
+
Views are not covered by 2.0 yet. Keep UIKit `View`, `Prop`, view `Events`, and `OnViewDidUpdateProps` DSL entries on 1.0. Macro declarations or expansion tests alone do not prove the runtime update path exists.
|
|
413
|
+
|
|
414
|
+
The planned shape is a class marked `@ExpoView` whose props and event callbacks are declared once in a typed `@ViewProps` struct, instead of split between the native view and a hand-written JS prop type. Until that lands complete, a module with views migrates its non-view members and keeps the view on the DSL. That is a normal mixed-mode result, not a failed migration.
|
|
264
415
|
|
|
265
416
|
Module lifecycle is core-owned rather than macro-generated. The DSL components map to hook methods with no-op defaults:
|
|
266
417
|
|
|
@@ -289,7 +440,7 @@ Before deleting `definition()`, verify that it contains no:
|
|
|
289
440
|
- views or view events
|
|
290
441
|
- lifecycle or app-context listeners
|
|
291
442
|
- queue-pinned functions
|
|
292
|
-
-
|
|
443
|
+
- `StaticFunction`/`StaticAsyncFunction` entries, unless core declares the `constructor:` decoration hook
|
|
293
444
|
|
|
294
445
|
## Contract checklist
|
|
295
446
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: expo-native-ui
|
|
3
|
-
description: Framework (OSS). Build beautiful, native-feeling Expo screens. Covers Apple HIG styling, semantic colors, native controls, SF Symbols, media,
|
|
3
|
+
description: Framework (OSS). Build beautiful, native-feeling Expo screens. Covers Apple HIG styling, semantic colors, native controls, SF Symbols, media, visual effects, gradients, storage, and responsive layout. For routing and navigation, use the expo-router skill; for motion and animation, use the expo-animation skill.
|
|
4
4
|
version: 1.1.1
|
|
5
5
|
license: MIT
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Expo Native UI Guidelines
|
|
9
9
|
|
|
10
|
-
For routes, links, stacks, tabs, modals, sheets, and headers, use the `expo-router` skill.
|
|
10
|
+
For routes, links, stacks, tabs, modals, sheets, and headers, use the `expo-router` skill. For any motion — entering/exiting, gestures, springs, keyboard-driven UI — use the `expo-animation` skill.
|
|
11
11
|
|
|
12
|
-
> **Before picking any UI component, check `expo-ui` first.** `@expo/ui` provides native equivalents — BottomSheet, Button, Picker, Slider, Menu, Section, Switch, SegmentedControl, and more — rendered as real SwiftUI on iOS and Jetpack Compose on Android, available in Expo Go on SDK 56+ with no custom build. Load the **`expo-ui`** skill to find the right component before falling back to React Native built-ins or community libraries. This skill (`expo-native-ui`) covers the surrounding structure: Expo Router navigation, layout, styling, and
|
|
12
|
+
> **Before picking any UI component, check `expo-ui` first.** `@expo/ui` provides native equivalents — BottomSheet, Button, Picker, Slider, Menu, Section, Switch, SegmentedControl, and more — rendered as real SwiftUI on iOS and Jetpack Compose on Android, available in Expo Go on SDK 56+ with no custom build. Load the **`expo-ui`** skill to find the right component before falling back to React Native built-ins or community libraries. This skill (`expo-native-ui`) covers the surrounding structure: Expo Router navigation, layout, styling, and visual effects.
|
|
13
13
|
|
|
14
14
|
## References
|
|
15
15
|
|
|
@@ -17,10 +17,9 @@ Consult these resources as needed:
|
|
|
17
17
|
|
|
18
18
|
```
|
|
19
19
|
references/
|
|
20
|
-
animations.md Reanimated: entering, exiting, layout, scroll-driven, gestures
|
|
21
20
|
controls.md Native iOS: Switch, Slider, SegmentedControl, DateTimePicker, Picker
|
|
22
21
|
gradients.md CSS gradients via experimental_backgroundImage (New Arch only)
|
|
23
|
-
icons.md SF Symbols via expo-
|
|
22
|
+
icons.md SF Symbols via expo-symbols SymbolView: names, weights, animations; Material icons on Android
|
|
24
23
|
media.md Camera, audio, video, and file saving
|
|
25
24
|
storage.md SQLite, AsyncStorage, SecureStore
|
|
26
25
|
visual-effects.md Blur (expo-blur) and liquid glass (expo-glass-effect)
|
|
@@ -48,12 +47,11 @@ You need `npx expo run:ios/android` or `eas build` ONLY when using:
|
|
|
48
47
|
|
|
49
48
|
### When Expo Go Works
|
|
50
49
|
|
|
51
|
-
Expo Go supports a
|
|
50
|
+
Expo Go supports a wide range of features out of the box:
|
|
52
51
|
|
|
53
|
-
-
|
|
54
|
-
- Expo Router navigation
|
|
52
|
+
- Most `expo-*` packages (camera, location, sensors, sqlite, etc.) — but not all: remote push notifications don't work in Expo Go on Android since SDK 53, and some packages need native capabilities Expo Go doesn't bundle (e.g. WebGPU — see `references/webgpu-three.md`)
|
|
53
|
+
- Expo Router navigation and deep links
|
|
55
54
|
- Most UI libraries (reanimated, gesture handler, etc.)
|
|
56
|
-
- Push notifications, deep links, and more
|
|
57
55
|
|
|
58
56
|
**If you're unsure, try Expo Go first.** Creating custom builds adds complexity, slower iteration, and requires Xcode/Android Studio setup.
|
|
59
57
|
|
|
@@ -72,7 +70,7 @@ Expo Go supports a huge range of features out of the box:
|
|
|
72
70
|
- Never use legacy expo-permissions
|
|
73
71
|
- `expo-audio` not `expo-av`
|
|
74
72
|
- `expo-video` not `expo-av`
|
|
75
|
-
- `expo-
|
|
73
|
+
- `expo-symbols` (`SymbolView`) for SF Symbols on iOS, not `@expo/vector-icons` — see `references/icons.md`. SF Symbols are Apple-only: on Android every icon needs a Material source (`md` prop on NativeTabs triggers; in-screen options under "Android: Material Icons" in icons.md), never SF-only iconography
|
|
76
74
|
- `react-native-safe-area-context` not react-native SafeAreaView
|
|
77
75
|
- `process.env.EXPO_OS` not `Platform.OS`
|
|
78
76
|
- `React.use` not `React.useContext`
|
|
@@ -83,7 +81,7 @@ Expo Go supports a huge range of features out of the box:
|
|
|
83
81
|
|
|
84
82
|
## Responsiveness
|
|
85
83
|
|
|
86
|
-
-
|
|
84
|
+
- Wrap screens with scrollable content in a ScrollView. Screens whose root is a FlatList/FlashList must not add an outer ScrollView (the list is the scroll container), and full-bleed screens (camera, map, canvas) need neither
|
|
87
85
|
- Use `<ScrollView contentInsetAdjustmentBehavior="automatic" />` instead of `<SafeAreaView>` for smarter safe area insets
|
|
88
86
|
- `contentInsetAdjustmentBehavior="automatic"` should be applied to FlatList and SectionList as well
|
|
89
87
|
- Use flexbox instead of Dimensions API
|
|
@@ -93,15 +91,21 @@ Expo Go supports a huge range of features out of the box:
|
|
|
93
91
|
|
|
94
92
|
- Use expo-haptics conditionally on iOS to make more delightful experiences
|
|
95
93
|
- Use views with built-in haptics like `<Switch />` from React Native and `@react-native-community/datetimepicker`
|
|
96
|
-
- When a route
|
|
97
|
-
- When adding a `ScrollView` to the page it should almost always be the first component inside the route component
|
|
94
|
+
- When a Stack route has scrollable content, make the ScrollView (or FlatList) the first component inside the route, with `contentInsetAdjustmentBehavior="automatic"` set
|
|
98
95
|
- Use the `<Text selectable />` prop on text containing data that could be copied
|
|
99
96
|
- Consider formatting large numbers like 1.4M or 38k
|
|
100
97
|
- Never use intrinsic elements like 'img' or 'div' unless in a webview or Expo DOM component
|
|
98
|
+
- Every screen that loads data has four states (loading, error, empty, content) - never show the empty state while the first load is still resolving; the rules live in the `expo-data-fetching` skill
|
|
99
|
+
- On scrollable forms and search results, use `keyboardShouldPersistTaps="handled"` so controls receive the first tap and unhandled taps can dismiss the keyboard. Use `"always"` only when unhandled taps should also keep it open
|
|
100
|
+
- A form's primary action must never sit under the keyboard. For UI that tracks the keyboard's real frame, load the `expo-animation` skill's keyboard recipe (`react-native-keyboard-controller`) - never `Keyboard.addListener` plus a timing animation
|
|
101
|
+
- Every enabled control must perform its advertised action: search filters results, Save commits edits, and settings affect behavior. Empty handlers and success alerts are not implementations; local state is enough when the user requested a prototype
|
|
102
|
+
- For async saves, preserve drafts and handle pending/failure states per `expo-data-fetching`; do not dismiss a form before its save succeeds
|
|
103
|
+
|
|
104
|
+
Before calling a screen complete, walk through its primary task, including one failure and recovery when it loads or saves data. Check keyboard access and back/dismiss behavior. Try long titles, missing images, no search results, and large system text; required actions must remain reachable. Report what you exercised and what you could not run.
|
|
101
105
|
|
|
102
106
|
# Styling
|
|
103
107
|
|
|
104
|
-
Follow Apple Human Interface Guidelines.
|
|
108
|
+
Follow each platform's own design language: Apple Human Interface Guidelines on iOS, Material Design 3 on Android. Never dress one platform in the other's uniform - no FAB or ripple in iOS layouts; no hand-built iOS chrome (back-chevrons, large-title text, iOS-styled switches) on Android.
|
|
105
109
|
|
|
106
110
|
## General Styling Rules
|
|
107
111
|
|
|
@@ -110,7 +114,7 @@ Follow Apple Human Interface Guidelines.
|
|
|
110
114
|
- Always account for safe area, either with stack headers, tabs, or ScrollView/FlatList `contentInsetAdjustmentBehavior="automatic"`
|
|
111
115
|
- Ensure both top and bottom safe area insets are accounted for
|
|
112
116
|
- Inline styles not StyleSheet.create unless reusing styles is faster
|
|
113
|
-
-
|
|
117
|
+
- For any motion or animation work, load the `expo-animation` skill — it owns the animate-or-not decision, timing values, and interruption rules
|
|
114
118
|
- Use `{ borderCurve: 'continuous' }` for rounded corners unless creating a capsule shape
|
|
115
119
|
- ALWAYS use a navigation stack title instead of a custom text element on the page
|
|
116
120
|
- When padding a ScrollView, use `contentContainerStyle` padding and gap instead of padding on the ScrollView itself (reduces clipping)
|
|
@@ -148,6 +152,11 @@ export const colors = {
|
|
|
148
152
|
android: Color.android.dynamic.surface,
|
|
149
153
|
default: "#ffffff",
|
|
150
154
|
})!,
|
|
155
|
+
secondarySystemBackground: Platform.select({
|
|
156
|
+
ios: Color.ios.secondarySystemBackground,
|
|
157
|
+
android: Color.android.dynamic.surfaceVariant,
|
|
158
|
+
default: "#f2f2f7",
|
|
159
|
+
})!,
|
|
151
160
|
systemBlue: Platform.select({
|
|
152
161
|
ios: Color.ios.systemBlue,
|
|
153
162
|
android: Color.android.dynamic.primary,
|
|
@@ -165,7 +174,7 @@ import { colors } from "@/theme/colors";
|
|
|
165
174
|
```
|
|
166
175
|
|
|
167
176
|
- iOS re-resolves these colors automatically when the system theme changes. On Android, call `useColorScheme()` inside any component that renders them so it re-renders when the theme flips (required when React Compiler memoizes the component).
|
|
168
|
-
- Don't pass `Color` / `PlatformColor` values into Reanimated styles — use static colors there
|
|
177
|
+
- Don't pass `Color` / `PlatformColor` values into Reanimated styles — they are opaque native color objects, not strings; use static colors there.
|
|
169
178
|
- `Platform.select({...})!` returns `string | OpaqueColorValue`. Most React Native style props accept `ColorValue` (`string | OpaqueColorValue`) so this works fine. But some third-party props only accept `string` (e.g. `tintColor` on `expo-image`). Cast when needed: `colors.label as string`.
|
|
170
179
|
|
|
171
180
|
## Text Styling
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
interface:
|
|
2
2
|
display_name: "Expo Native UI"
|
|
3
|
-
short_description: "Build beautiful, native-feeling Expo screens: HIG styling, semantic colors, controls, SF Symbols, media,
|
|
4
|
-
default_prompt: "Use $expo-native-ui to style native-feeling Expo screens, choose semantic colors and native controls, add media
|
|
3
|
+
short_description: "Build beautiful, native-feeling Expo screens: HIG styling, semantic colors, controls, SF Symbols, media, and visual effects"
|
|
4
|
+
default_prompt: "Use $expo-native-ui to style native-feeling Expo screens, choose semantic colors and native controls, add media and visual effects, and decide when Expo Go is enough before creating native builds."
|
|
@@ -17,17 +17,7 @@ const [enabled, setEnabled] = useState(false);
|
|
|
17
17
|
<Switch value={enabled} onValueChange={setEnabled} />;
|
|
18
18
|
```
|
|
19
19
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
```tsx
|
|
23
|
-
<Switch
|
|
24
|
-
value={enabled}
|
|
25
|
-
onValueChange={setEnabled}
|
|
26
|
-
trackColor={{ false: "#767577", true: "#81b0ff" }}
|
|
27
|
-
thumbColor={enabled ? "#f5dd4b" : "#f4f3f4"}
|
|
28
|
-
ios_backgroundColor="#3e3e3e"
|
|
29
|
-
/>
|
|
30
|
-
```
|
|
20
|
+
Don't recolor the Switch — native styling adapts to dark mode automatically.
|
|
31
21
|
|
|
32
22
|
## Segmented Control
|
|
33
23
|
|
|
@@ -83,21 +73,6 @@ const [value, setValue] = useState(0.5);
|
|
|
83
73
|
/>;
|
|
84
74
|
```
|
|
85
75
|
|
|
86
|
-
### Customization
|
|
87
|
-
|
|
88
|
-
```tsx
|
|
89
|
-
<Slider
|
|
90
|
-
value={value}
|
|
91
|
-
onValueChange={setValue}
|
|
92
|
-
minimumValue={0}
|
|
93
|
-
maximumValue={100}
|
|
94
|
-
step={1}
|
|
95
|
-
minimumTrackTintColor="#007AFF"
|
|
96
|
-
maximumTrackTintColor="#E5E5EA"
|
|
97
|
-
thumbTintColor="#007AFF"
|
|
98
|
-
/>
|
|
99
|
-
```
|
|
100
|
-
|
|
101
76
|
### Discrete Steps
|
|
102
77
|
|
|
103
78
|
```tsx
|
|
@@ -174,39 +149,22 @@ const [date, setDate] = useState(new Date());
|
|
|
174
149
|
/>
|
|
175
150
|
```
|
|
176
151
|
|
|
177
|
-
## Stepper
|
|
178
|
-
|
|
179
|
-
Increment/decrement numeric values.
|
|
180
|
-
|
|
181
|
-
```tsx
|
|
182
|
-
import { Stepper } from "react-native";
|
|
183
|
-
import { useState } from "react";
|
|
184
|
-
|
|
185
|
-
const [count, setCount] = useState(0);
|
|
186
|
-
|
|
187
|
-
<Stepper
|
|
188
|
-
value={count}
|
|
189
|
-
onValueChange={setCount}
|
|
190
|
-
minimumValue={0}
|
|
191
|
-
maximumValue={10}
|
|
192
|
-
/>;
|
|
193
|
-
```
|
|
194
|
-
|
|
195
152
|
## TextInput
|
|
196
153
|
|
|
197
154
|
Native text input with various keyboard types.
|
|
198
155
|
|
|
199
156
|
```tsx
|
|
200
157
|
import { TextInput } from "react-native";
|
|
158
|
+
import { colors } from "@/theme/colors";
|
|
201
159
|
|
|
202
160
|
<TextInput
|
|
203
161
|
placeholder="Enter text..."
|
|
204
|
-
placeholderTextColor=
|
|
162
|
+
placeholderTextColor={colors.secondaryLabel as string}
|
|
205
163
|
style={{
|
|
206
164
|
padding: 12,
|
|
207
165
|
fontSize: 16,
|
|
208
166
|
borderRadius: 8,
|
|
209
|
-
backgroundColor:
|
|
167
|
+
backgroundColor: colors.secondarySystemBackground,
|
|
210
168
|
}}
|
|
211
169
|
/>
|
|
212
170
|
```
|
|
@@ -265,6 +223,7 @@ const [selected, setSelected] = useState("js");
|
|
|
265
223
|
## Best Practices
|
|
266
224
|
|
|
267
225
|
- **Haptics**: Switch and DateTimePicker have built-in haptics — don't add extra
|
|
226
|
+
- **No native Stepper**: React Native has no `Stepper` component — compose increment/decrement from two Buttons or a numeric TextInput
|
|
268
227
|
- **Accessibility**: Native controls have proper accessibility labels by default
|
|
269
228
|
- **Dark Mode**: Avoid custom colors — native styling adapts automatically
|
|
270
229
|
- **Spacing**: Use consistent padding around controls (12-16pt)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Icons (SF Symbols)
|
|
2
2
|
|
|
3
|
-
Use SF Symbols for native feel. Never use FontAwesome or Ionicons.
|
|
3
|
+
Use SF Symbols on iOS for native feel. SF Symbols are an Apple asset and never render on Android - every icon needs a Material counterpart (see "Android: Material Icons" below). Never use FontAwesome or Ionicons on either platform, and never emoji as icons.
|
|
4
4
|
|
|
5
5
|
## Basic Usage
|
|
6
6
|
|
|
@@ -198,6 +198,25 @@ Some symbols support multiple colors:
|
|
|
198
198
|
/>
|
|
199
199
|
```
|
|
200
200
|
|
|
201
|
+
## Android: Material Icons
|
|
202
|
+
|
|
203
|
+
On Android an SF Symbol source renders nothing - an app that ships SF-only iconography ships blank icons. Give every icon a Material source:
|
|
204
|
+
|
|
205
|
+
- **Tab bars**: pass `md` (Material Symbol name) beside `sf` on NativeTabs triggers - `<NativeTabs.Trigger.Icon sf="gear" md="settings" />` (SDK 55+, see the `expo-router` skill).
|
|
206
|
+
- **In screens**: `@expo/vector-icons` Material families, gated by platform:
|
|
207
|
+
|
|
208
|
+
```tsx
|
|
209
|
+
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
|
|
210
|
+
|
|
211
|
+
process.env.EXPO_OS === "ios" ? (
|
|
212
|
+
<SymbolView name="gear" tintColor={colors.label} style={{ width: 24, height: 24 }} />
|
|
213
|
+
) : (
|
|
214
|
+
<MaterialIcons name="settings" size={24} color={colors.label} />
|
|
215
|
+
);
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
One icon family per platform - never a mixed set, never SF names on Android or Material glyphs on iOS.
|
|
219
|
+
|
|
201
220
|
## Finding Symbol Names
|
|
202
221
|
|
|
203
222
|
1. Use the SF Symbols app on macOS (free from Apple)
|
|
@@ -206,7 +225,7 @@ Some symbols support multiple colors:
|
|
|
206
225
|
|
|
207
226
|
## Best Practices
|
|
208
227
|
|
|
209
|
-
-
|
|
228
|
+
- On iOS, always use SF Symbols over vector icon libraries; on Android, use Material icons (see above)
|
|
210
229
|
- Match symbol weight to nearby text weight
|
|
211
230
|
- Use `.fill` variants for selected/active states
|
|
212
231
|
- Use the cross-platform `colors` helper (see SKILL.md "Colors") for tint to support dark mode
|
|
@@ -6,17 +6,17 @@
|
|
|
6
6
|
- Ensure to flip the camera with `mirror` to emulate social apps
|
|
7
7
|
- Use liquid glass buttons on cameras
|
|
8
8
|
- Icons: `arrow.triangle.2.circlepath` (flip), `photo` (gallery), `bolt` (flash)
|
|
9
|
-
-
|
|
10
|
-
- Lazily request media library permission
|
|
9
|
+
- Gate the camera behind an explanation screen with a Grant button (as below) — never fire the permission prompt on mount
|
|
10
|
+
- Lazily request media library permission (at first save/pick)
|
|
11
|
+
|
|
12
|
+
The example uses the `GlassButton` helper defined in `visual-effects.md` (Glass Buttons section).
|
|
11
13
|
|
|
12
14
|
```tsx
|
|
13
15
|
import React, { useRef, useState } from "react";
|
|
14
|
-
import { View,
|
|
16
|
+
import { View, Pressable, Text } from "react-native";
|
|
15
17
|
import { CameraView, CameraType, useCameraPermissions } from "expo-camera";
|
|
16
|
-
import * as MediaLibrary from "expo-media-library";
|
|
17
18
|
import * as ImagePicker from "expo-image-picker";
|
|
18
19
|
import * as Haptics from "expo-haptics";
|
|
19
|
-
import { SymbolView } from "expo-symbols";
|
|
20
20
|
import { colors } from "@/theme/colors";
|
|
21
21
|
import { GlassView } from "expo-glass-effect";
|
|
22
22
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
|
@@ -32,9 +32,9 @@ function Camera({ onPicture }: { onPicture: (uri: string) => Promise<void> }) {
|
|
|
32
32
|
<View style={{ flex: 1, justifyContent: "center", alignItems: "center", backgroundColor: colors.systemBackground }}>
|
|
33
33
|
<Text style={{ color: colors.label, padding: 16 }}>Camera access is required</Text>
|
|
34
34
|
<GlassView isInteractive tintColor={colors.systemBlue} style={{ borderRadius: 12 }}>
|
|
35
|
-
<
|
|
35
|
+
<Pressable onPress={requestPermission} style={{ padding: 12, borderRadius: 12 }}>
|
|
36
36
|
<Text style={{ color: "white" }}>Grant Permission</Text>
|
|
37
|
-
</
|
|
37
|
+
</Pressable>
|
|
38
38
|
</GlassView>
|
|
39
39
|
</View>
|
|
40
40
|
);
|
|
@@ -64,7 +64,7 @@ function Camera({ onPicture }: { onPicture: (uri: string) => Promise<void> }) {
|
|
|
64
64
|
<CameraView ref={cameraRef} mirror style={{ flex: 1 }} facing={type} />
|
|
65
65
|
<View style={{ position: "absolute", left: 0, right: 0, bottom: bottom, gap: 16, alignItems: "center" }}>
|
|
66
66
|
<GlassView isInteractive style={{ padding: 8, borderRadius: 99 }}>
|
|
67
|
-
<
|
|
67
|
+
<Pressable onPress={takePhoto} style={{ width: 64, height: 64, borderRadius: 99, backgroundColor: "white" }} />
|
|
68
68
|
</GlassView>
|
|
69
69
|
<View style={{ flexDirection: "row", justifyContent: "space-around", paddingHorizontal: 8 }}>
|
|
70
70
|
<GlassButton onPress={selectPhoto} icon="photo" />
|
|
@@ -98,31 +98,26 @@ import {
|
|
|
98
98
|
setAudioModeAsync,
|
|
99
99
|
useAudioRecorderState,
|
|
100
100
|
} from 'expo-audio';
|
|
101
|
-
import { useEffect } from 'react';
|
|
102
101
|
import { Alert, Button } from 'react-native';
|
|
103
102
|
|
|
104
103
|
function App() {
|
|
105
104
|
const audioRecorder = useAudioRecorder(RecordingPresets.HIGH_QUALITY);
|
|
106
105
|
const recorderState = useAudioRecorderState(audioRecorder);
|
|
107
106
|
|
|
107
|
+
// Request the microphone on first record, not on mount — no prompt before user intent
|
|
108
108
|
const record = async () => {
|
|
109
|
+
const status = await AudioModule.requestRecordingPermissionsAsync();
|
|
110
|
+
if (!status.granted) {
|
|
111
|
+
Alert.alert('Permission to access microphone was denied');
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
await setAudioModeAsync({ playsInSilentMode: true, allowsRecording: true });
|
|
109
115
|
await audioRecorder.prepareToRecordAsync();
|
|
110
116
|
audioRecorder.record();
|
|
111
117
|
};
|
|
112
118
|
|
|
113
119
|
const stop = () => audioRecorder.stop();
|
|
114
120
|
|
|
115
|
-
useEffect(() => {
|
|
116
|
-
(async () => {
|
|
117
|
-
const status = await AudioModule.requestRecordingPermissionsAsync();
|
|
118
|
-
if (status.granted) {
|
|
119
|
-
setAudioModeAsync({ playsInSilentMode: true, allowsRecording: true });
|
|
120
|
-
} else {
|
|
121
|
-
Alert.alert('Permission to access microphone was denied');
|
|
122
|
-
}
|
|
123
|
-
})();
|
|
124
|
-
}, []);
|
|
125
|
-
|
|
126
121
|
return (
|
|
127
122
|
<Button
|
|
128
123
|
title={recorderState.isRecording ? 'Stop' : 'Start'}
|
|
@@ -142,24 +142,22 @@ function GlassButton({ icon, onPress }) {
|
|
|
142
142
|
|
|
143
143
|
### Checking Availability
|
|
144
144
|
|
|
145
|
+
Check both guards: `isLiquidGlassAvailable()` (OS support) and `isGlassEffectAPIAvailable()` — some iOS 26 beta versions lack the API and crash without the second check.
|
|
146
|
+
|
|
145
147
|
```tsx
|
|
146
|
-
import { isLiquidGlassAvailable } from "expo-glass-effect";
|
|
148
|
+
import { isLiquidGlassAvailable, isGlassEffectAPIAvailable } from "expo-glass-effect";
|
|
147
149
|
|
|
148
|
-
|
|
149
|
-
// Use GlassView
|
|
150
|
-
} else {
|
|
151
|
-
// Fallback to BlurView or solid background
|
|
152
|
-
}
|
|
150
|
+
const canUseGlass = isLiquidGlassAvailable() && isGlassEffectAPIAvailable();
|
|
153
151
|
```
|
|
154
152
|
|
|
155
153
|
### Fallback Pattern
|
|
156
154
|
|
|
157
155
|
```tsx
|
|
158
|
-
import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";
|
|
156
|
+
import { GlassView, isLiquidGlassAvailable, isGlassEffectAPIAvailable } from "expo-glass-effect";
|
|
159
157
|
import { BlurView } from "expo-blur";
|
|
160
158
|
|
|
161
159
|
function AdaptiveGlass({ children, style }) {
|
|
162
|
-
if (isLiquidGlassAvailable()) {
|
|
160
|
+
if (isLiquidGlassAvailable() && isGlassEffectAPIAvailable()) {
|
|
163
161
|
return <GlassView style={style}>{children}</GlassView>;
|
|
164
162
|
}
|
|
165
163
|
|
|
@@ -171,6 +169,8 @@ function AdaptiveGlass({ children, style }) {
|
|
|
171
169
|
}
|
|
172
170
|
```
|
|
173
171
|
|
|
172
|
+
When the user has Reduce Transparency enabled (`AccessibilityInfo.isReduceTransparencyEnabled()`), skip glass *and* blur — render a solid `colors.systemBackground` surface instead.
|
|
173
|
+
|
|
174
174
|
## Sheet with Glass Background
|
|
175
175
|
|
|
176
176
|
Make sheet backgrounds liquid glass on iOS 26+:
|
|
@@ -190,8 +190,9 @@ Make sheet backgrounds liquid glass on iOS 26+:
|
|
|
190
190
|
## Best Practices
|
|
191
191
|
|
|
192
192
|
- Use `systemMaterial` tints for automatic dark mode support
|
|
193
|
-
- Always set `overflow: 'hidden'` on BlurView for rounded corners
|
|
194
|
-
- Use `isInteractive` on GlassView for buttons
|
|
195
|
-
- Check `isLiquidGlassAvailable()` and provide fallbacks
|
|
193
|
+
- Always set `overflow: 'hidden'` on BlurView for rounded corners — but never on a `GlassView` or its ancestors: glass clips itself via `borderRadius` (+ `borderCurve`), and outside clipping cuts off the rim highlight and press bulge
|
|
194
|
+
- Use `isInteractive` on GlassView only for actual controls (buttons, pressables) — leave background surfaces non-interactive
|
|
195
|
+
- Check `isLiquidGlassAvailable()` **and** `isGlassEffectAPIAvailable()`, provide fallbacks, and respect Reduce Transparency with a solid surface
|
|
196
|
+
- Never animate opacity on a `GlassView` or an ancestor — animate content around a stable glass surface, or switch `glassEffectStyle` on the mounted view to change its look
|
|
196
197
|
- Avoid nesting blur views (performance impact)
|
|
197
198
|
- Keep blur intensity reasonable (50-100) for readability
|