Expo 54 (#8931)
* update deps * update patches * fix new arch optout * build from source, disable new arch * update to preview.14 * upgrade off of preview to release * fix lockfile lint * update patches * add back expo-location * downgrade reanimated * downgrade pagerview * use expo-file-system/legacy * try and upgrade types/react-dom * fix type of uint8array * rm @types/react resolution from bad rebase * more type errors * fix mock * bump rn-compressor * add patch of https://github.com/bluesky-social/react-native-paste-input/pull/5 * delete NativeDropdown & remove Zeego * `import React` in expo modules * bump cocoapods * rm unused picker lib * add speculative react-native-mmkv patch * revert patch, add temp debug info to error * bring back speculative patch, patch built JS code instead of source * revert patch, just do debugging part * dep update * update resolutions, dynamic-app-icon * bump node version in eas.json * update build instructions * rm atob from docs * Delete react-native-mmkv+2.12.2.patch * bump expo which might include fix * update expo patch version * bump expo patch version * wrong version oops * update expo to latest * update react-native-compressor, rm patch
This commit is contained in:
@@ -53,12 +53,12 @@ jobs:
|
|||||||
|
|
||||||
- uses: maxim-lobanov/setup-xcode@v1
|
- uses: maxim-lobanov/setup-xcode@v1
|
||||||
with:
|
with:
|
||||||
xcode-version: '16.4'
|
xcode-version: "16.4"
|
||||||
|
|
||||||
- name: ☕️ Setup Cocoapods
|
- name: ☕️ Setup Cocoapods
|
||||||
uses: maxim-lobanov/setup-cocoapods@v1
|
uses: maxim-lobanov/setup-cocoapods@v1
|
||||||
with:
|
with:
|
||||||
version: 1.14.3
|
version: 1.16.2
|
||||||
|
|
||||||
- name: 💾 Cache Pods
|
- name: 💾 Cache Pods
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
|
|||||||
@@ -192,12 +192,12 @@ jobs:
|
|||||||
|
|
||||||
- uses: maxim-lobanov/setup-xcode@v1
|
- uses: maxim-lobanov/setup-xcode@v1
|
||||||
with:
|
with:
|
||||||
xcode-version: '16.2'
|
xcode-version: "16.2"
|
||||||
|
|
||||||
- name: ☕️ Setup Cocoapods
|
- name: ☕️ Setup Cocoapods
|
||||||
uses: maxim-lobanov/setup-cocoapods@v1
|
uses: maxim-lobanov/setup-cocoapods@v1
|
||||||
with:
|
with:
|
||||||
version: 1.14.3
|
version: 1.16.2
|
||||||
|
|
||||||
- name: 💾 Cache Pods
|
- name: 💾 Cache Pods
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v3
|
||||||
@@ -293,8 +293,8 @@ jobs:
|
|||||||
|
|
||||||
- uses: actions/setup-java@v4
|
- uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: 'temurin'
|
distribution: "temurin"
|
||||||
java-version: '17'
|
java-version: "17"
|
||||||
|
|
||||||
- name: ⚙️ Install dependencies
|
- name: ⚙️ Install dependencies
|
||||||
run: yarn install
|
run: yarn install
|
||||||
@@ -332,7 +332,7 @@ jobs:
|
|||||||
id: timestamp
|
id: timestamp
|
||||||
uses: nanzm/get-time-action@master
|
uses: nanzm/get-time-action@master
|
||||||
with:
|
with:
|
||||||
format: 'MM-DD-HH-mm-ss'
|
format: "MM-DD-HH-mm-ss"
|
||||||
|
|
||||||
- name: 🚀 Upload Artifact
|
- name: 🚀 Upload Artifact
|
||||||
id: upload-artifact
|
id: upload-artifact
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {createDownloadResumable, deleteAsync} from 'expo-file-system'
|
import {createDownloadResumable, deleteAsync} from 'expo-file-system/legacy'
|
||||||
import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
|
import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
|||||||
+2
-2
@@ -43,6 +43,7 @@ module.exports = function (_config) {
|
|||||||
icon: './assets/app-icons/ios_icon_default_light.png',
|
icon: './assets/app-icons/ios_icon_default_light.png',
|
||||||
userInterfaceStyle: 'automatic',
|
userInterfaceStyle: 'automatic',
|
||||||
primaryColor: '#1083fe',
|
primaryColor: '#1083fe',
|
||||||
|
newArchEnabled: false,
|
||||||
ios: {
|
ios: {
|
||||||
supportsTablet: false,
|
supportsTablet: false,
|
||||||
bundleIdentifier: 'xyz.blueskyweb.app',
|
bundleIdentifier: 'xyz.blueskyweb.app',
|
||||||
@@ -208,13 +209,12 @@ module.exports = function (_config) {
|
|||||||
{
|
{
|
||||||
ios: {
|
ios: {
|
||||||
deploymentTarget: '15.1',
|
deploymentTarget: '15.1',
|
||||||
newArchEnabled: false,
|
buildReactNativeFromSource: true,
|
||||||
},
|
},
|
||||||
android: {
|
android: {
|
||||||
compileSdkVersion: 35,
|
compileSdkVersion: 35,
|
||||||
targetSdkVersion: 35,
|
targetSdkVersion: 35,
|
||||||
buildToolsVersion: '35.0.0',
|
buildToolsVersion: '35.0.0',
|
||||||
newArchEnabled: false,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
+1
-2
@@ -63,7 +63,7 @@ After you do `yarn ios` and `yarn android` once, you can later just run `yarn we
|
|||||||
### Tips
|
### Tips
|
||||||
|
|
||||||
- Copy the `.env.example` to `.env` and fill in any necessary tokens. (The Sentry token is NOT required; see instructions below if you want to enable Sentry.)
|
- Copy the `.env.example` to `.env` and fill in any necessary tokens. (The Sentry token is NOT required; see instructions below if you want to enable Sentry.)
|
||||||
- To run on the device, add `--device` to the command (e.g. `yarn android --device`). To build in production mode (slower build, faster app), also add `--variant release`.
|
- To run on the device, add `--device` to the command (e.g. `yarn android --device`). To build in production mode (slower build, faster app), also add `--variant release` on Android or `--configuration Release` on iOS.
|
||||||
- If you want to use Expo EAS on your own builds without ejecting from Expo, make sure to change the `owner` and `extra.eas.projectId` properties. If you do not have an Expo account, you may remove these properties.
|
- If you want to use Expo EAS on your own builds without ejecting from Expo, make sure to change the `owner` and `extra.eas.projectId` properties. If you do not have an Expo account, you may remove these properties.
|
||||||
- `npx react-native info` Checks what has been installed.
|
- `npx react-native info` Checks what has been installed.
|
||||||
- If the Android simulator frequently hangs or is very sluggish, [bump its memory limit](https://stackoverflow.com/a/40068396)
|
- If the Android simulator frequently hangs or is very sluggish, [bump its memory limit](https://stackoverflow.com/a/40068396)
|
||||||
@@ -164,7 +164,6 @@ See [testing.md](./testing.md).
|
|||||||
- TextEncoder / TextDecoder
|
- TextEncoder / TextDecoder
|
||||||
- react-native-url-polyfill
|
- react-native-url-polyfill
|
||||||
- Array#findLast (on web)
|
- Array#findLast (on web)
|
||||||
- atob (on native)
|
|
||||||
|
|
||||||
### Sentry sourcemaps
|
### Sentry sourcemaps
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"base": {
|
"base": {
|
||||||
"node": "18.18.2"
|
"node": "20.19.4"
|
||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"extends": "base",
|
"extends": "base",
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ jest.mock('react-native-safe-area-context', () => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
jest.mock('expo-file-system', () => ({
|
jest.mock('expo-file-system/legacy', () => ({
|
||||||
getInfoAsync: jest.fn().mockResolvedValue({exists: true, size: 100}),
|
getInfoAsync: jest.fn().mockResolvedValue({exists: true, size: 100}),
|
||||||
deleteAsync: jest.fn(),
|
deleteAsync: jest.fn(),
|
||||||
createDownloadResumable: jest.fn(),
|
createDownloadResumable: jest.fn(),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import ExpoModulesCore
|
import ExpoModulesCore
|
||||||
|
import React
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class SheetView: ExpoView, UISheetPresentationControllerDelegate {
|
class SheetView: ExpoView, UISheetPresentationControllerDelegate {
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import ExpoModulesCore
|
import ExpoModulesCore
|
||||||
|
import React
|
||||||
|
|
||||||
// This view will be used as a native component. Make sure to inherit from `ExpoView`
|
// This view will be used as a native component. Make sure to inherit from `ExpoView`
|
||||||
// to apply the proper styling (e.g. border radius and shadows).
|
// to apply the proper styling (e.g. border radius and shadows).
|
||||||
|
|||||||
+57
-62
@@ -92,15 +92,13 @@
|
|||||||
"@lingui/react": "^4.14.1",
|
"@lingui/react": "^4.14.1",
|
||||||
"@mattermost/react-native-paste-input": "mattermost/react-native-paste-input",
|
"@mattermost/react-native-paste-input": "mattermost/react-native-paste-input",
|
||||||
"@miblanchard/react-native-slider": "^2.6.0",
|
"@miblanchard/react-native-slider": "^2.6.0",
|
||||||
"@mozzius/expo-dynamic-app-icon": "^1.7.1",
|
"@mozzius/expo-dynamic-app-icon": "^1.8.0",
|
||||||
"@react-native-async-storage/async-storage": "2.1.2",
|
"@react-native-async-storage/async-storage": "2.2.0",
|
||||||
"@react-native-menu/menu": "^1.2.3",
|
|
||||||
"@react-native-picker/picker": "2.11.0",
|
|
||||||
"@react-navigation/bottom-tabs": "^7.3.13",
|
"@react-navigation/bottom-tabs": "^7.3.13",
|
||||||
"@react-navigation/drawer": "^7.3.12",
|
"@react-navigation/drawer": "^7.3.12",
|
||||||
"@react-navigation/native": "^7.1.9",
|
"@react-navigation/native": "^7.1.9",
|
||||||
"@react-navigation/native-stack": "^7.3.13",
|
"@react-navigation/native-stack": "^7.3.13",
|
||||||
"@sentry/react-native": "~6.14.0",
|
"@sentry/react-native": "~6.20.0",
|
||||||
"@tanstack/query-async-storage-persister": "^5.25.0",
|
"@tanstack/query-async-storage-persister": "^5.25.0",
|
||||||
"@tanstack/react-query": "^5.8.1",
|
"@tanstack/react-query": "^5.8.1",
|
||||||
"@tanstack/react-query-persist-client": "^5.25.0",
|
"@tanstack/react-query-persist-client": "^5.25.0",
|
||||||
@@ -126,41 +124,40 @@
|
|||||||
"bcp-47": "^2.1.0",
|
"bcp-47": "^2.1.0",
|
||||||
"bcp-47-match": "^2.0.3",
|
"bcp-47-match": "^2.0.3",
|
||||||
"date-fns": "^2.30.0",
|
"date-fns": "^2.30.0",
|
||||||
"deprecated-react-native-prop-types": "^5.0.0",
|
|
||||||
"email-validator": "^2.0.4",
|
"email-validator": "^2.0.4",
|
||||||
"emoji-mart": "^5.5.2",
|
"emoji-mart": "^5.5.2",
|
||||||
"emoji-regex": "^10.4.0",
|
"emoji-regex": "^10.4.0",
|
||||||
"eventemitter3": "^5.0.1",
|
"eventemitter3": "^5.0.1",
|
||||||
"expo": "53.0.11",
|
"expo": "^54.0.9",
|
||||||
"expo-application": "~6.1.4",
|
"expo-application": "~7.0.7",
|
||||||
"expo-blur": "~14.1.5",
|
"expo-blur": "~15.0.7",
|
||||||
"expo-build-properties": "~0.14.6",
|
"expo-build-properties": "~1.0.9",
|
||||||
"expo-camera": "~16.1.8",
|
"expo-camera": "~17.0.8",
|
||||||
"expo-clipboard": "~7.1.4",
|
"expo-clipboard": "~8.0.7",
|
||||||
"expo-dev-client": "~5.2.0",
|
"expo-dev-client": "~6.0.12",
|
||||||
"expo-device": "~7.1.4",
|
"expo-device": "~8.0.8",
|
||||||
"expo-file-system": "~18.1.10",
|
"expo-file-system": "~19.0.14",
|
||||||
"expo-font": "~13.3.1",
|
"expo-font": "~14.0.8",
|
||||||
"expo-haptics": "~14.1.4",
|
"expo-haptics": "~15.0.7",
|
||||||
"expo-image": "^2.4.0",
|
"expo-image": "~3.0.8",
|
||||||
"expo-image-crop-tool": "^0.1.8",
|
"expo-image-crop-tool": "^0.1.8",
|
||||||
"expo-image-manipulator": "~13.1.7",
|
"expo-image-manipulator": "~14.0.7",
|
||||||
"expo-image-picker": "17.0.5",
|
"expo-image-picker": "~17.0.8",
|
||||||
"expo-intent-launcher": "^12.1.5",
|
"expo-intent-launcher": "~13.0.7",
|
||||||
"expo-linear-gradient": "~14.1.5",
|
"expo-linear-gradient": "~15.0.7",
|
||||||
"expo-linking": "~7.1.5",
|
"expo-linking": "~8.0.8",
|
||||||
"expo-localization": "~16.1.5",
|
"expo-localization": "~17.0.7",
|
||||||
"expo-location": "~18.1.6",
|
"expo-location": "~19.0.7",
|
||||||
"expo-media-library": "~17.1.7",
|
"expo-media-library": "~18.2.0",
|
||||||
"expo-notifications": "~0.31.3",
|
"expo-notifications": "~0.32.11",
|
||||||
"expo-screen-orientation": "~8.1.7",
|
"expo-screen-orientation": "~9.0.7",
|
||||||
"expo-sharing": "~13.1.5",
|
"expo-sharing": "~14.0.7",
|
||||||
"expo-splash-screen": "~0.30.9",
|
"expo-splash-screen": "~31.0.10",
|
||||||
"expo-system-ui": "~5.0.8",
|
"expo-system-ui": "~6.0.7",
|
||||||
"expo-task-manager": "~13.1.5",
|
"expo-task-manager": "~14.0.7",
|
||||||
"expo-updates": "~0.28.14",
|
"expo-updates": "~29.0.11",
|
||||||
"expo-video": "~2.2.1",
|
"expo-video": "~3.0.11",
|
||||||
"expo-web-browser": "~14.1.6",
|
"expo-web-browser": "~15.0.7",
|
||||||
"fast-text-encoding": "^1.0.6",
|
"fast-text-encoding": "^1.0.6",
|
||||||
"history": "^5.3.0",
|
"history": "^5.3.0",
|
||||||
"hls.js": "^1.6.2",
|
"hls.js": "^1.6.2",
|
||||||
@@ -178,37 +175,36 @@
|
|||||||
"patch-package": "^6.5.1",
|
"patch-package": "^6.5.1",
|
||||||
"postinstall-postinstall": "^2.1.0",
|
"postinstall-postinstall": "^2.1.0",
|
||||||
"psl": "^1.9.0",
|
"psl": "^1.9.0",
|
||||||
"radix-ui": "^1.2.0",
|
"radix-ui": "^1.4.3",
|
||||||
"react": "19.0.0",
|
"react": "19.1.0",
|
||||||
"react-compiler-runtime": "^19.1.0-rc.1",
|
"react-compiler-runtime": "^19.1.0-rc.1",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.1.0",
|
||||||
"react-image-crop": "^11.0.7",
|
"react-image-crop": "^11.0.7",
|
||||||
"react-is": "19",
|
"react-is": "19",
|
||||||
"react-keyed-flatten-children": "^5.0.0",
|
"react-keyed-flatten-children": "^5.0.0",
|
||||||
"react-native": "^0.79.3",
|
"react-native": "0.81.4",
|
||||||
"react-native-compressor": "^1.11.0",
|
"react-native-compressor": "^1.13.0",
|
||||||
"react-native-date-picker": "^5.0.12",
|
"react-native-date-picker": "^5.0.13",
|
||||||
"react-native-device-attest": "^0.1.6",
|
"react-native-device-attest": "^0.1.6",
|
||||||
"react-native-drawer-layout": "^4.1.8",
|
"react-native-drawer-layout": "^4.1.8",
|
||||||
"react-native-edge-to-edge": "^1.6.0",
|
"react-native-edge-to-edge": "^1.6.0",
|
||||||
"react-native-gesture-handler": "2.25.0",
|
"react-native-gesture-handler": "~2.28.0",
|
||||||
"react-native-get-random-values": "~1.11.0",
|
"react-native-get-random-values": "~1.11.0",
|
||||||
"react-native-ios-context-menu": "^1.15.3",
|
"react-native-keyboard-controller": "1.18.5",
|
||||||
"react-native-keyboard-controller": "^1.17.5",
|
|
||||||
"react-native-mmkv": "^2.12.2",
|
"react-native-mmkv": "^2.12.2",
|
||||||
"react-native-pager-view": "6.8.0",
|
"react-native-pager-view": "6.8.0",
|
||||||
"react-native-progress": "bluesky-social/react-native-progress",
|
"react-native-progress": "bluesky-social/react-native-progress",
|
||||||
"react-native-qrcode-styled": "^0.3.3",
|
"react-native-qrcode-styled": "^0.3.3",
|
||||||
"react-native-reanimated": "~3.17.5",
|
"react-native-reanimated": "^3.19.1",
|
||||||
"react-native-root-siblings": "^5.0.1",
|
"react-native-root-siblings": "^5.0.1",
|
||||||
"react-native-safe-area-context": "5.4.0",
|
"react-native-safe-area-context": "~5.6.0",
|
||||||
"react-native-screens": "^4.11.1",
|
"react-native-screens": "~4.16.0",
|
||||||
"react-native-svg": "15.12.0",
|
"react-native-svg": "15.12.1",
|
||||||
"react-native-uitextview": "^1.4.0",
|
"react-native-uitextview": "^1.4.0",
|
||||||
"react-native-url-polyfill": "^1.3.0",
|
"react-native-url-polyfill": "^1.3.0",
|
||||||
"react-native-uuid": "^2.0.3",
|
"react-native-uuid": "^2.0.3",
|
||||||
"react-native-view-shot": "^4.0.3",
|
"react-native-view-shot": "^4.0.3",
|
||||||
"react-native-web": "~0.20.0",
|
"react-native-web": "^0.21.0",
|
||||||
"react-native-web-webview": "^1.0.2",
|
"react-native-web-webview": "^1.0.2",
|
||||||
"react-native-webview": "^13.13.5",
|
"react-native-webview": "^13.13.5",
|
||||||
"react-remove-scroll-bar": "^2.3.8",
|
"react-remove-scroll-bar": "^2.3.8",
|
||||||
@@ -220,7 +216,6 @@
|
|||||||
"tippy.js": "^6.3.7",
|
"tippy.js": "^6.3.7",
|
||||||
"tlds": "^1.234.0",
|
"tlds": "^1.234.0",
|
||||||
"tldts": "^6.1.46",
|
"tldts": "^6.1.46",
|
||||||
"zeego": "^1.6.2",
|
|
||||||
"zod": "^3.20.2"
|
"zod": "^3.20.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -228,13 +223,13 @@
|
|||||||
"@babel/core": "^7.26.0",
|
"@babel/core": "^7.26.0",
|
||||||
"@babel/preset-env": "^7.26.0",
|
"@babel/preset-env": "^7.26.0",
|
||||||
"@babel/runtime": "^7.26.0",
|
"@babel/runtime": "^7.26.0",
|
||||||
"@expo/config-plugins": "~10.0.2",
|
"@expo/config-plugins": "~54.0.1",
|
||||||
"@lingui/cli": "^4.14.1",
|
"@lingui/cli": "^4.14.1",
|
||||||
"@lingui/macro": "^4.14.1",
|
"@lingui/macro": "^4.14.1",
|
||||||
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
|
"@pmmmwh/react-refresh-webpack-plugin": "^0.5.15",
|
||||||
"@react-native/babel-preset": "0.79.3",
|
"@react-native/babel-preset": "0.81.4",
|
||||||
"@react-native/eslint-config": "^0.79.3",
|
"@react-native/eslint-config": "^0.81.4",
|
||||||
"@react-native/typescript-config": "^0.79.3",
|
"@react-native/typescript-config": "^0.81.4",
|
||||||
"@sentry/webpack-plugin": "^3.2.2",
|
"@sentry/webpack-plugin": "^3.2.2",
|
||||||
"@testing-library/jest-native": "^5.4.3",
|
"@testing-library/jest-native": "^5.4.3",
|
||||||
"@testing-library/react-native": "^13.2.0",
|
"@testing-library/react-native": "^13.2.0",
|
||||||
@@ -245,14 +240,14 @@
|
|||||||
"@types/lodash.shuffle": "^4.2.7",
|
"@types/lodash.shuffle": "^4.2.7",
|
||||||
"@types/psl": "^1.1.1",
|
"@types/psl": "^1.1.1",
|
||||||
"@types/react": "^19.1.12",
|
"@types/react": "^19.1.12",
|
||||||
"@types/react-dom": "^19.1.8",
|
"@types/react-dom": "^19.1.9",
|
||||||
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
"@typescript-eslint/eslint-plugin": "^7.18.0",
|
||||||
"@typescript-eslint/parser": "^7.18.0",
|
"@typescript-eslint/parser": "^7.18.0",
|
||||||
"babel-jest": "^29.7.0",
|
"babel-jest": "^29.7.0",
|
||||||
"babel-plugin-macros": "^3.1.0",
|
"babel-plugin-macros": "^3.1.0",
|
||||||
"babel-plugin-module-resolver": "^5.0.2",
|
"babel-plugin-module-resolver": "^5.0.2",
|
||||||
"babel-plugin-react-compiler": "^19.1.0-rc.1",
|
"babel-plugin-react-compiler": "^19.1.0-rc.1",
|
||||||
"babel-preset-expo": "~13.1.11",
|
"babel-preset-expo": "~54.0.0",
|
||||||
"eslint": "^8.19.0",
|
"eslint": "^8.19.0",
|
||||||
"eslint-plugin-bsky-internal": "link:./eslint",
|
"eslint-plugin-bsky-internal": "link:./eslint",
|
||||||
"eslint-plugin-ft-flow": "^2.0.3",
|
"eslint-plugin-ft-flow": "^2.0.3",
|
||||||
@@ -266,7 +261,7 @@
|
|||||||
"husky": "^8.0.3",
|
"husky": "^8.0.3",
|
||||||
"is-ci": "^3.0.1",
|
"is-ci": "^3.0.1",
|
||||||
"jest": "^29.7.0",
|
"jest": "^29.7.0",
|
||||||
"jest-expo": "~53.0.7",
|
"jest-expo": "~54.0.12",
|
||||||
"jest-junit": "^16.0.0",
|
"jest-junit": "^16.0.0",
|
||||||
"lint-staged": "^13.2.3",
|
"lint-staged": "^13.2.3",
|
||||||
"lockfile-lint": "^4.14.0",
|
"lockfile-lint": "^4.14.0",
|
||||||
@@ -276,14 +271,14 @@
|
|||||||
"react-refresh": "^0.14.0",
|
"react-refresh": "^0.14.0",
|
||||||
"svgo": "^3.3.2",
|
"svgo": "^3.3.2",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"typescript": "~5.8.3",
|
"typescript": "^5.9.2",
|
||||||
"webpack-bundle-analyzer": "^4.10.1"
|
"webpack-bundle-analyzer": "^4.10.1"
|
||||||
},
|
},
|
||||||
"resolutions": {
|
"resolutions": {
|
||||||
"@expo/image-utils": "0.6.3",
|
"@react-native/babel-preset": "0.81.4",
|
||||||
"@react-native/babel-preset": "0.79.3",
|
"@react-native/normalize-colors": "0.81.4",
|
||||||
"@react-native/normalize-colors": "0.79.3",
|
"**/@expo/image-utils": "0.8.7",
|
||||||
"**/expo-constants": "17.0.3",
|
"**/expo-constants": "18.0.8",
|
||||||
"**/expo-device": "7.1.4",
|
"**/expo-device": "7.1.4",
|
||||||
"**/zod": "3.23.8",
|
"**/zod": "3.23.8",
|
||||||
"**/multiformats": "9.9.0"
|
"**/multiformats": "9.9.0"
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
diff --git a/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt b/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt
|
||||||
|
index 4ed2307..ede1181 100644
|
||||||
|
--- a/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt
|
||||||
|
+++ b/node_modules/@mattermost/react-native-paste-input/android/src/main/java/com/mattermost/pasteinputtext/PasteTextInputManager.kt
|
||||||
|
@@ -54,7 +54,7 @@ class PasteTextInputManager(context: ReactApplicationContext) : ReactTextInputMa
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getExportedCustomBubblingEventTypeConstants(): MutableMap<String, Any> {
|
||||||
|
- val map = super.getExportedCustomBubblingEventTypeConstants()!!
|
||||||
|
+ val map = super.getExportedCustomBubblingEventTypeConstants().toMutableMap()
|
||||||
|
map["onPaste"] = MapBuilder.of(
|
||||||
|
"phasedRegistrationNames",
|
||||||
|
MapBuilder.of("bubbled", "onPaste")
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
diff --git a/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt b/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt
|
|
||||||
index f1255e8..a9b49e5 100644
|
|
||||||
--- a/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt
|
|
||||||
+++ b/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt
|
|
||||||
@@ -112,11 +112,9 @@ class MediaLibraryModule : Module() {
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncFunction("createAssetAsync") { localUri: String, albumId: String?, promise: Promise ->
|
|
||||||
- throwUnlessPermissionsGranted {
|
|
||||||
- withModuleScope(promise) {
|
|
||||||
- CreateAssetWithAlbumId(context, localUri, promise, true, albumId)
|
|
||||||
- .execute()
|
|
||||||
- }
|
|
||||||
+ withModuleScope(promise) {
|
|
||||||
+ CreateAssetWithAlbumId(context, localUri, promise, true, albumId)
|
|
||||||
+ .execute()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
diff --git a/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt b/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt
|
||||||
|
index 2dc1db2..ee844e8 100644
|
||||||
|
--- a/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt
|
||||||
|
+++ b/node_modules/expo-media-library/android/src/main/java/expo/modules/medialibrary/MediaLibraryModule.kt
|
||||||
|
@@ -105,7 +105,7 @@ class MediaLibraryModule : Module() {
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncFunction("createAssetAsync") Coroutine { localUri: String, albumId: String? ->
|
||||||
|
- requireSystemPermissions()
|
||||||
|
+ // requireSystemPermissions()
|
||||||
|
return@Coroutine createAssetWithAlbumId(context, localUri, true, albumId)
|
||||||
|
}
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ diff --git a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/Scro
|
|||||||
index d029337..0f63ea3 100644
|
index d029337..0f63ea3 100644
|
||||||
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
|
--- a/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
|
||||||
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
|
+++ b/node_modules/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm
|
||||||
@@ -1003,6 +1003,11 @@ - (void)_adjustForMaintainVisibleContentPosition
|
@@ -1038,6 +1038,11 @@ - (void)_adjustForMaintainVisibleContentPosition
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
diff --git a/node_modules/react-native-compressor/ios/Video/VideoMain.swift b/node_modules/react-native-compressor/ios/Video/VideoMain.swift
|
|
||||||
index 6fe5e9c..af8a0b5 100644
|
|
||||||
--- a/node_modules/react-native-compressor/ios/Video/VideoMain.swift
|
|
||||||
+++ b/node_modules/react-native-compressor/ios/Video/VideoMain.swift
|
|
||||||
@@ -1,5 +1,4 @@
|
|
||||||
import Foundation
|
|
||||||
-import AssetsLibrary
|
|
||||||
import AVFoundation
|
|
||||||
import Photos
|
|
||||||
import MobileCoreServices
|
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
diff --git a/node_modules/react-native-date-picker/ios/RNDatePicker.h b/node_modules/react-native-date-picker/ios/RNDatePicker.h
|
||||||
|
index 480746e..470dc3a 100644
|
||||||
|
--- a/node_modules/react-native-date-picker/ios/RNDatePicker.h
|
||||||
|
+++ b/node_modules/react-native-date-picker/ios/RNDatePicker.h
|
||||||
|
@@ -15,6 +15,7 @@ NS_ASSUME_NONNULL_END
|
||||||
|
#else
|
||||||
|
#import "DatePicker.h"
|
||||||
|
#import <UIKit/UIKit.h>
|
||||||
|
+#include <string>
|
||||||
|
|
||||||
|
@interface RNDatePicker : DatePicker
|
||||||
|
|
||||||
|
@@ -22,4 +23,3 @@ NS_ASSUME_NONNULL_END
|
||||||
|
@end
|
||||||
|
|
||||||
|
#endif
|
||||||
|
-
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
diff --git a/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm b/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm
|
|
||||||
index 43d11b4..5bf0d32 100644
|
|
||||||
--- a/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm
|
|
||||||
+++ b/node_modules/react-native-gesture-handler/apple/RNGestureHandler.mm
|
|
||||||
@@ -461,16 +461,23 @@ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
|
||||||
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
|
|
||||||
shouldRequireFailureOfGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
|
|
||||||
{
|
|
||||||
- if ([_handlersToWaitFor count]) {
|
|
||||||
- RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer];
|
|
||||||
- if (handler != nil) {
|
|
||||||
- for (NSNumber *handlerTag in _handlersToWaitFor) {
|
|
||||||
- if ([handler.tag isEqual:handlerTag]) {
|
|
||||||
- return YES;
|
|
||||||
- }
|
|
||||||
- }
|
|
||||||
+ RNGestureHandler *handler = [RNGestureHandler findGestureHandlerByRecognizer:otherGestureRecognizer];
|
|
||||||
+ if (handler == nil) {
|
|
||||||
+ return NO;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ for (NSNumber *handlerTag in _handlersToWaitFor) {
|
|
||||||
+ if ([handler.tag isEqual:handlerTag]) {
|
|
||||||
+ return YES;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+
|
|
||||||
+ for (NSNumber *handlerTag in handler->_handlersThatShouldWait) {
|
|
||||||
+ if ([_tag isEqual:handlerTag]) {
|
|
||||||
+ return YES;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
return NO;
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
diff --git a/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.cpp b/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.cpp
|
|
||||||
index eae3989..432745a 100644
|
|
||||||
--- a/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.cpp
|
|
||||||
+++ b/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.cpp
|
|
||||||
@@ -416,6 +416,10 @@ void NativeProxy::progressLayoutAnimation(
|
|
||||||
tag, newPropsJNI, isSharedTransition);
|
|
||||||
}
|
|
||||||
|
|
||||||
+void NativeProxy::endLayoutAnimation(int tag, bool shouldRemove) {
|
|
||||||
+ layoutAnimations_->cthis()->endLayoutAnimation(tag, shouldRemove);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
PlatformDepMethodsHolder NativeProxy::getPlatformDependentMethods() {
|
|
||||||
#ifdef RCT_NEW_ARCH_ENABLED
|
|
||||||
// nothing
|
|
||||||
@@ -455,14 +459,7 @@ PlatformDepMethodsHolder NativeProxy::getPlatformDependentMethods() {
|
|
||||||
auto progressLayoutAnimation =
|
|
||||||
bindThis(&NativeProxy::progressLayoutAnimation);
|
|
||||||
|
|
||||||
- auto endLayoutAnimation = [weakThis = weak_from_this()](
|
|
||||||
- int tag, bool removeView) {
|
|
||||||
- auto strongThis = weakThis.lock();
|
|
||||||
- if (!strongThis) {
|
|
||||||
- return;
|
|
||||||
- }
|
|
||||||
- strongThis->layoutAnimations_->cthis()->endLayoutAnimation(tag, removeView);
|
|
||||||
- };
|
|
||||||
+ auto endLayoutAnimation = bindThis(&NativeProxy::endLayoutAnimation);
|
|
||||||
|
|
||||||
auto maybeFlushUiUpdatesQueueFunction =
|
|
||||||
bindThis(&NativeProxy::maybeFlushUIUpdatesQueue);
|
|
||||||
diff --git a/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.h b/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.h
|
|
||||||
index 2ee2cc8..2edb5c9 100644
|
|
||||||
--- a/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.h
|
|
||||||
+++ b/node_modules/react-native-reanimated/android/src/main/cpp/reanimated/android/NativeProxy.h
|
|
||||||
@@ -234,6 +234,8 @@ class NativeProxy : public jni::HybridClass<NativeProxy>,
|
|
||||||
const jsi::Object &newProps,
|
|
||||||
bool isSharedTransition);
|
|
||||||
|
|
||||||
+ void endLayoutAnimation(int tag, bool shouldRemove);
|
|
||||||
+
|
|
||||||
/***
|
|
||||||
* Wraps a method of `NativeProxy` in a function object capturing `this`
|
|
||||||
* @tparam TReturn return type of passed method
|
|
||||||
@@ -2,8 +2,6 @@ import {
|
|||||||
Children,
|
Children,
|
||||||
cloneElement,
|
cloneElement,
|
||||||
isValidElement,
|
isValidElement,
|
||||||
type ReactElement,
|
|
||||||
type ReactNode,
|
|
||||||
useCallback,
|
useCallback,
|
||||||
useEffect,
|
useEffect,
|
||||||
useMemo,
|
useMemo,
|
||||||
@@ -26,7 +24,7 @@ import {useA11y} from '#/state/a11y'
|
|||||||
* screen reader support is enabled. THIS SHOULD BE USED SPARINGLY, only when
|
* screen reader support is enabled. THIS SHOULD BE USED SPARINGLY, only when
|
||||||
* no better option is available.
|
* no better option is available.
|
||||||
*/
|
*/
|
||||||
export function FocusScope({children}: {children: ReactNode}) {
|
export function FocusScope({children}: {children: React.ReactNode}) {
|
||||||
const {screenReaderEnabled} = useA11y()
|
const {screenReaderEnabled} = useA11y()
|
||||||
|
|
||||||
return screenReaderEnabled ? <FocusTrap>{children}</FocusTrap> : children
|
return screenReaderEnabled ? <FocusTrap>{children}</FocusTrap> : children
|
||||||
@@ -41,7 +39,7 @@ export function FocusScope({children}: {children: ReactNode}) {
|
|||||||
* they have reached the start or end of the content and tell them how to
|
* they have reached the start or end of the content and tell them how to
|
||||||
* remain within the active content section.
|
* remain within the active content section.
|
||||||
*/
|
*/
|
||||||
function FocusTrap({children}: {children: ReactNode}) {
|
function FocusTrap({children}: {children: React.ReactNode}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const child = useRef<View>(null)
|
const child = useRef<View>(null)
|
||||||
|
|
||||||
@@ -53,7 +51,7 @@ function FocusTrap({children}: {children: ReactNode}) {
|
|||||||
const decoratedChildren = useMemo(() => {
|
const decoratedChildren = useMemo(() => {
|
||||||
return Children.toArray(children).map((node, i) => {
|
return Children.toArray(children).map((node, i) => {
|
||||||
if (i === 0 && isValidElement(node)) {
|
if (i === 0 && isValidElement(node)) {
|
||||||
const n = node as ReactElement<any>
|
const n = node as React.ReactElement<any>
|
||||||
if (n.props.ref !== undefined) {
|
if (n.props.ref !== undefined) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'FocusScope needs to override the ref on its first child.',
|
'FocusScope needs to override the ref on its first child.',
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import {type ReactNode} from 'react'
|
|
||||||
import {FocusScope as RadixFocusScope} from 'radix-ui/internal'
|
import {FocusScope as RadixFocusScope} from 'radix-ui/internal'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -6,7 +5,7 @@ import {FocusScope as RadixFocusScope} from 'radix-ui/internal'
|
|||||||
* use this in Dialogs and such already. It's here as a convenient counterpart
|
* use this in Dialogs and such already. It's here as a convenient counterpart
|
||||||
* to the hacky native solution.
|
* to the hacky native solution.
|
||||||
*/
|
*/
|
||||||
export function FocusScope({children}: {children: ReactNode}) {
|
export function FocusScope({children}: {children: React.ReactNode}) {
|
||||||
return (
|
return (
|
||||||
<RadixFocusScope.FocusScope loop asChild trapped>
|
<RadixFocusScope.FocusScope loop asChild trapped>
|
||||||
{children}
|
{children}
|
||||||
|
|||||||
@@ -72,9 +72,9 @@ export function ActionsWrapper({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<View
|
<View
|
||||||
// @ts-expect-error web only
|
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
|
// @ts-expect-error web only
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onMouseLeave}
|
onBlur={onMouseLeave}
|
||||||
style={[a.flex_1, isFromSelf ? a.flex_row : a.flex_row_reverse]}
|
style={[a.flex_1, isFromSelf ? a.flex_row : a.flex_row_reverse]}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {copyAsync} from 'expo-file-system'
|
import {copyAsync} from 'expo-file-system/legacy'
|
||||||
import {type BskyAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api'
|
import {type BskyAgent, type ComAtprotoRepoUploadBlob} from '@atproto/api'
|
||||||
|
|
||||||
import {safeDeleteAsync} from '#/lib/media/manip'
|
import {safeDeleteAsync} from '#/lib/media/manip'
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import {
|
|||||||
makeDirectoryAsync,
|
makeDirectoryAsync,
|
||||||
StorageAccessFramework,
|
StorageAccessFramework,
|
||||||
writeAsStringAsync,
|
writeAsStringAsync,
|
||||||
} from 'expo-file-system'
|
} from 'expo-file-system/legacy'
|
||||||
import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
|
import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
|
||||||
import * as MediaLibrary from 'expo-media-library'
|
import * as MediaLibrary from 'expo-media-library'
|
||||||
import * as Sharing from 'expo-sharing'
|
import * as Sharing from 'expo-sharing'
|
||||||
|
|||||||
@@ -157,7 +157,7 @@ function createResizedImage(
|
|||||||
|
|
||||||
export async function saveBytesToDisk(
|
export async function saveBytesToDisk(
|
||||||
filename: string,
|
filename: string,
|
||||||
bytes: Uint8Array,
|
bytes: Uint8Array<ArrayBuffer>,
|
||||||
type: string,
|
type: string,
|
||||||
) {
|
) {
|
||||||
const blob = new Blob([bytes], {type})
|
const blob = new Blob([bytes], {type})
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
documentDirectory,
|
documentDirectory,
|
||||||
getInfoAsync,
|
getInfoAsync,
|
||||||
readDirectoryAsync,
|
readDirectoryAsync,
|
||||||
} from 'expo-file-system'
|
} from 'expo-file-system/legacy'
|
||||||
import ExpoImageCropTool, {type OpenCropperOptions} from 'expo-image-crop-tool'
|
import ExpoImageCropTool, {type OpenCropperOptions} from 'expo-image-crop-tool'
|
||||||
|
|
||||||
import {compressIfNeeded} from './manip'
|
import {compressIfNeeded} from './manip'
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {createUploadTask, FileSystemUploadType} from 'expo-file-system'
|
import {createUploadTask, FileSystemUploadType} from 'expo-file-system/legacy'
|
||||||
import {type AppBskyVideoDefs, type BskyAgent} from '@atproto/api'
|
import {type AppBskyVideoDefs, type BskyAgent} from '@atproto/api'
|
||||||
import {type I18n} from '@lingui/core'
|
import {type I18n} from '@lingui/core'
|
||||||
import {msg} from '@lingui/macro'
|
import {msg} from '@lingui/macro'
|
||||||
|
|||||||
@@ -333,9 +333,9 @@ function ChatListItemReady({
|
|||||||
return (
|
return (
|
||||||
<GestureActionView actions={actions}>
|
<GestureActionView actions={actions}>
|
||||||
<View
|
<View
|
||||||
// @ts-expect-error web only
|
|
||||||
onMouseEnter={onMouseEnter}
|
onMouseEnter={onMouseEnter}
|
||||||
onMouseLeave={onMouseLeave}
|
onMouseLeave={onMouseLeave}
|
||||||
|
// @ts-expect-error web only
|
||||||
onFocus={onFocus}
|
onFocus={onFocus}
|
||||||
onBlur={onMouseLeave}
|
onBlur={onMouseLeave}
|
||||||
style={[a.relative, t.atoms.bg]}>
|
style={[a.relative, t.atoms.bg]}>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {useMemo} from 'react'
|
import {useMemo} from 'react'
|
||||||
import {Platform} from 'react-native'
|
import {Platform} from 'react-native'
|
||||||
import {setStringAsync} from 'expo-clipboard'
|
import {setStringAsync} from 'expo-clipboard'
|
||||||
import * as FileSystem from 'expo-file-system'
|
import * as FileSystem from 'expo-file-system/legacy'
|
||||||
import {Image} from 'expo-image'
|
import {Image} from 'expo-image'
|
||||||
import {msg, Trans} from '@lingui/macro'
|
import {msg, Trans} from '@lingui/macro'
|
||||||
import {useLingui} from '@lingui/react'
|
import {useLingui} from '@lingui/react'
|
||||||
|
|||||||
@@ -420,7 +420,7 @@ function DevOptions() {
|
|||||||
{
|
{
|
||||||
style: 'default',
|
style: 'default',
|
||||||
text: 'Apply',
|
text: 'Apply',
|
||||||
onPress: channel => {
|
onPress: (channel?: string) => {
|
||||||
tryApplyUpdate(channel ?? '')
|
tryApplyUpdate(channel ?? '')
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {
|
|||||||
deleteAsync,
|
deleteAsync,
|
||||||
makeDirectoryAsync,
|
makeDirectoryAsync,
|
||||||
moveAsync,
|
moveAsync,
|
||||||
} from 'expo-file-system'
|
} from 'expo-file-system/legacy'
|
||||||
import {
|
import {
|
||||||
type Action,
|
type Action,
|
||||||
type ActionCrop,
|
type ActionCrop,
|
||||||
|
|||||||
@@ -1,340 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {
|
|
||||||
Platform,
|
|
||||||
Pressable,
|
|
||||||
StyleSheet,
|
|
||||||
View,
|
|
||||||
type ViewStyle,
|
|
||||||
} from 'react-native'
|
|
||||||
import {type IconProp} from '@fortawesome/fontawesome-svg-core'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import * as DropdownMenu from 'zeego/dropdown-menu'
|
|
||||||
import {type MenuItemCommonProps} from 'zeego/lib/typescript/menu'
|
|
||||||
|
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
|
||||||
import {useTheme} from '#/lib/ThemeContext'
|
|
||||||
import {isIOS} from '#/platform/detection'
|
|
||||||
import {Portal} from '#/components/Portal'
|
|
||||||
|
|
||||||
// Custom Dropdown Menu Components
|
|
||||||
// ==
|
|
||||||
/**
|
|
||||||
* @deprecated use Menu from `#/components/Menu.tsx` instead
|
|
||||||
*/
|
|
||||||
export const DropdownMenuRoot = DropdownMenu.Root
|
|
||||||
// export const DropdownMenuTrigger = DropdownMenu.Trigger
|
|
||||||
/**
|
|
||||||
* @deprecated use Menu from `#/components/Menu.tsx` instead
|
|
||||||
*/
|
|
||||||
export const DropdownMenuContent = DropdownMenu.Content
|
|
||||||
|
|
||||||
type TriggerProps = Omit<
|
|
||||||
React.ComponentProps<(typeof DropdownMenu)['Trigger']>,
|
|
||||||
'children'
|
|
||||||
> &
|
|
||||||
React.PropsWithChildren<{
|
|
||||||
testID?: string
|
|
||||||
accessibilityLabel?: string
|
|
||||||
accessibilityHint?: string
|
|
||||||
}>
|
|
||||||
/**
|
|
||||||
* @deprecated use Menu from `#/components/Menu.tsx` instead
|
|
||||||
*/
|
|
||||||
export const DropdownMenuTrigger = DropdownMenu.create(
|
|
||||||
(props: TriggerProps) => {
|
|
||||||
const theme = useTheme()
|
|
||||||
const defaultCtrlColor = theme.palette.default.postCtrl
|
|
||||||
|
|
||||||
return (
|
|
||||||
// This Pressable doesn't actually do anything other than
|
|
||||||
// provide the "pressed state" visual feedback.
|
|
||||||
<Pressable
|
|
||||||
testID={props.testID}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={props.accessibilityLabel}
|
|
||||||
accessibilityHint={props.accessibilityHint}
|
|
||||||
style={({pressed}) => [{opacity: pressed ? 0.8 : 1}]}>
|
|
||||||
<DropdownMenu.Trigger action="press">
|
|
||||||
<View>
|
|
||||||
{props.children ? (
|
|
||||||
props.children
|
|
||||||
) : (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon="ellipsis"
|
|
||||||
size={20}
|
|
||||||
color={defaultCtrlColor}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
</DropdownMenu.Trigger>
|
|
||||||
</Pressable>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
'Trigger',
|
|
||||||
)
|
|
||||||
|
|
||||||
type ItemProps = React.ComponentProps<(typeof DropdownMenu)['Item']>
|
|
||||||
/**
|
|
||||||
* @deprecated use Menu from `#/components/Menu.tsx` instead
|
|
||||||
*/
|
|
||||||
export const DropdownMenuItem = DropdownMenu.create(
|
|
||||||
(props: ItemProps & {testID?: string}) => {
|
|
||||||
const theme = useTheme()
|
|
||||||
const [focused, setFocused] = React.useState(false)
|
|
||||||
const backgroundColor = theme.colorScheme === 'dark' ? '#fff1' : '#0001'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownMenu.Item
|
|
||||||
{...props}
|
|
||||||
style={[styles.item, focused && {backgroundColor: backgroundColor}]}
|
|
||||||
onFocus={() => {
|
|
||||||
setFocused(true)
|
|
||||||
props.onFocus && props.onFocus()
|
|
||||||
}}
|
|
||||||
onBlur={() => {
|
|
||||||
setFocused(false)
|
|
||||||
props.onBlur && props.onBlur()
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
'Item',
|
|
||||||
)
|
|
||||||
|
|
||||||
type TitleProps = React.ComponentProps<(typeof DropdownMenu)['ItemTitle']>
|
|
||||||
/**
|
|
||||||
* @deprecated use Menu from `#/components/Menu.tsx` instead
|
|
||||||
*/
|
|
||||||
export const DropdownMenuItemTitle = DropdownMenu.create(
|
|
||||||
(props: TitleProps) => {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
return (
|
|
||||||
<DropdownMenu.ItemTitle
|
|
||||||
{...props}
|
|
||||||
style={[props.style, pal.text, styles.itemTitle]}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
'ItemTitle',
|
|
||||||
)
|
|
||||||
|
|
||||||
type IconProps = React.ComponentProps<(typeof DropdownMenu)['ItemIcon']>
|
|
||||||
/**
|
|
||||||
* @deprecated use Menu from `#/components/Menu.tsx` instead
|
|
||||||
*/
|
|
||||||
export const DropdownMenuItemIcon = DropdownMenu.create((props: IconProps) => {
|
|
||||||
return <DropdownMenu.ItemIcon {...props} />
|
|
||||||
}, 'ItemIcon')
|
|
||||||
|
|
||||||
type SeparatorProps = React.ComponentProps<(typeof DropdownMenu)['Separator']>
|
|
||||||
/**
|
|
||||||
* @deprecated use Menu from `#/components/Menu.tsx` instead
|
|
||||||
*/
|
|
||||||
export const DropdownMenuSeparator = DropdownMenu.create(
|
|
||||||
(props: SeparatorProps) => {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const theme = useTheme()
|
|
||||||
const {borderColor: separatorColor} =
|
|
||||||
theme.colorScheme === 'dark' ? pal.borderDark : pal.border
|
|
||||||
return (
|
|
||||||
<DropdownMenu.Separator
|
|
||||||
{...props}
|
|
||||||
style={[
|
|
||||||
props.style,
|
|
||||||
styles.separator,
|
|
||||||
{backgroundColor: separatorColor},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
},
|
|
||||||
'Separator',
|
|
||||||
)
|
|
||||||
|
|
||||||
// Types for Dropdown Menu and Items
|
|
||||||
export type DropdownItem = {
|
|
||||||
label: string | 'separator'
|
|
||||||
onPress?: () => void
|
|
||||||
testID?: string
|
|
||||||
icon?: {
|
|
||||||
ios: MenuItemCommonProps['ios']
|
|
||||||
android: string
|
|
||||||
web: IconProp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type Props = {
|
|
||||||
items: DropdownItem[]
|
|
||||||
testID?: string
|
|
||||||
accessibilityLabel?: string
|
|
||||||
accessibilityHint?: string
|
|
||||||
triggerStyle?: ViewStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The `NativeDropdown` function uses native iOS and Android dropdown menus.
|
|
||||||
* It also creates a animated custom dropdown for web that uses
|
|
||||||
* Radix UI primitives under the hood
|
|
||||||
* @prop {DropdownItem[]} items - An array of dropdown items
|
|
||||||
* @prop {React.ReactNode} children - A custom dropdown trigger
|
|
||||||
*
|
|
||||||
* @deprecated use Menu from `#/components/Menu.tsx` instead
|
|
||||||
*/
|
|
||||||
export function NativeDropdown({
|
|
||||||
items,
|
|
||||||
children,
|
|
||||||
testID,
|
|
||||||
accessibilityLabel,
|
|
||||||
accessibilityHint,
|
|
||||||
}: React.PropsWithChildren<Props>) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const theme = useTheme()
|
|
||||||
const [isOpen, setIsOpen] = React.useState(false)
|
|
||||||
const dropDownBackgroundColor =
|
|
||||||
theme.colorScheme === 'dark' ? pal.btn : pal.viewLight
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{isIOS && isOpen && (
|
|
||||||
<Portal>
|
|
||||||
<Backdrop />
|
|
||||||
</Portal>
|
|
||||||
)}
|
|
||||||
<DropdownMenuRoot onOpenWillChange={setIsOpen}>
|
|
||||||
<DropdownMenuTrigger
|
|
||||||
action="press"
|
|
||||||
testID={testID}
|
|
||||||
accessibilityLabel={accessibilityLabel}
|
|
||||||
accessibilityHint={accessibilityHint}>
|
|
||||||
{children}
|
|
||||||
</DropdownMenuTrigger>
|
|
||||||
{/* @ts-ignore inheriting props from Radix, which is only for web */}
|
|
||||||
<DropdownMenuContent
|
|
||||||
style={[styles.content, dropDownBackgroundColor]}
|
|
||||||
loop>
|
|
||||||
{items.map((item, index) => {
|
|
||||||
if (item.label === 'separator') {
|
|
||||||
return (
|
|
||||||
<DropdownMenuSeparator
|
|
||||||
key={getKey(item.label, index, item.testID)}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (index > 1 && items[index - 1].label === 'separator') {
|
|
||||||
return (
|
|
||||||
<DropdownMenu.Group
|
|
||||||
key={getKey(item.label, index, item.testID)}>
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={getKey(item.label, index, item.testID)}
|
|
||||||
onSelect={item.onPress}>
|
|
||||||
<DropdownMenuItemTitle>{item.label}</DropdownMenuItemTitle>
|
|
||||||
{item.icon && (
|
|
||||||
<DropdownMenuItemIcon
|
|
||||||
ios={item.icon.ios}
|
|
||||||
// androidIconName={item.icon.android} TODO: Add custom android icon support, because these ones are based on https://developer.android.com/reference/android/R.drawable.html and they are ugly
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={item.icon.web}
|
|
||||||
size={20}
|
|
||||||
style={[pal.text]}
|
|
||||||
/>
|
|
||||||
</DropdownMenuItemIcon>
|
|
||||||
)}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenu.Group>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={getKey(item.label, index, item.testID)}
|
|
||||||
onSelect={item.onPress}>
|
|
||||||
<DropdownMenuItemTitle>{item.label}</DropdownMenuItemTitle>
|
|
||||||
{item.icon && (
|
|
||||||
<DropdownMenuItemIcon
|
|
||||||
ios={item.icon.ios}
|
|
||||||
// androidIconName={item.icon.android}
|
|
||||||
>
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={item.icon.web}
|
|
||||||
size={20}
|
|
||||||
style={[pal.text]}
|
|
||||||
/>
|
|
||||||
</DropdownMenuItemIcon>
|
|
||||||
)}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</DropdownMenuContent>
|
|
||||||
</DropdownMenuRoot>
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function Backdrop() {
|
|
||||||
// Not visible but it eats the click outside.
|
|
||||||
// Only necessary for iOS.
|
|
||||||
return (
|
|
||||||
<Pressable
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel="Dialog backdrop"
|
|
||||||
accessibilityHint="Press the backdrop to close the dialog"
|
|
||||||
style={{
|
|
||||||
top: 0,
|
|
||||||
left: 0,
|
|
||||||
right: 0,
|
|
||||||
bottom: 0,
|
|
||||||
position: 'absolute',
|
|
||||||
}}
|
|
||||||
onPress={() => {
|
|
||||||
/* noop */
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getKey = (label: string, index: number, id?: string) => {
|
|
||||||
if (id) {
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
return `${label}_${index}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
separator: {
|
|
||||||
height: 1,
|
|
||||||
marginVertical: 4,
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
backgroundColor: '#f0f0f0',
|
|
||||||
borderRadius: 8,
|
|
||||||
paddingVertical: 4,
|
|
||||||
paddingHorizontal: 4,
|
|
||||||
marginTop: 6,
|
|
||||||
...Platform.select({
|
|
||||||
web: {
|
|
||||||
animationDuration: '400ms',
|
|
||||||
animationTimingFunction: 'cubic-bezier(0.16, 1, 0.3, 1)',
|
|
||||||
willChange: 'transform, opacity',
|
|
||||||
animationKeyframes: {
|
|
||||||
'0%': {opacity: 0, transform: [{scale: 0.5}]},
|
|
||||||
'100%': {opacity: 1, transform: [{scale: 1}]},
|
|
||||||
},
|
|
||||||
boxShadow:
|
|
||||||
'0px 10px 38px -10px rgba(22, 23, 24, 0.35), 0px 10px 20px -15px rgba(22, 23, 24, 0.2)',
|
|
||||||
transformOrigin: 'var(--radix-dropdown-menu-content-transform-origin)',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
item: {
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
columnGap: 20,
|
|
||||||
// @ts-ignore -web
|
|
||||||
cursor: 'pointer',
|
|
||||||
paddingVertical: 8,
|
|
||||||
paddingHorizontal: 12,
|
|
||||||
borderRadius: 8,
|
|
||||||
},
|
|
||||||
itemTitle: {
|
|
||||||
fontSize: 18,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,286 +0,0 @@
|
|||||||
import React from 'react'
|
|
||||||
import {
|
|
||||||
Pressable,
|
|
||||||
StyleSheet,
|
|
||||||
Text,
|
|
||||||
type View,
|
|
||||||
type ViewStyle,
|
|
||||||
} from 'react-native'
|
|
||||||
import {type IconProp} from '@fortawesome/fontawesome-svg-core'
|
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
|
||||||
import {DropdownMenu} from 'radix-ui'
|
|
||||||
import {type MenuItemCommonProps} from 'zeego/lib/typescript/menu'
|
|
||||||
|
|
||||||
import {HITSLOP_10} from '#/lib/constants'
|
|
||||||
import {usePalette} from '#/lib/hooks/usePalette'
|
|
||||||
import {useTheme} from '#/lib/ThemeContext'
|
|
||||||
|
|
||||||
// Custom Dropdown Menu Components
|
|
||||||
// ==
|
|
||||||
export const DropdownMenuRoot = DropdownMenu.Root
|
|
||||||
export const DropdownMenuContent = DropdownMenu.Content
|
|
||||||
|
|
||||||
type ItemProps = React.ComponentProps<(typeof DropdownMenu)['Item']>
|
|
||||||
export const DropdownMenuItem = (props: ItemProps & {testID?: string}) => {
|
|
||||||
const theme = useTheme()
|
|
||||||
const [focused, setFocused] = React.useState(false)
|
|
||||||
const backgroundColor = theme.colorScheme === 'dark' ? '#fff1' : '#0001'
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownMenu.Item
|
|
||||||
className="nativeDropdown-item"
|
|
||||||
{...props}
|
|
||||||
style={StyleSheet.flatten([
|
|
||||||
styles.item,
|
|
||||||
focused && {backgroundColor: backgroundColor},
|
|
||||||
])}
|
|
||||||
onFocus={() => {
|
|
||||||
setFocused(true)
|
|
||||||
}}
|
|
||||||
onBlur={() => {
|
|
||||||
setFocused(false)
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Types for Dropdown Menu and Items
|
|
||||||
export type DropdownItem = {
|
|
||||||
label: string | 'separator'
|
|
||||||
onPress?: () => void
|
|
||||||
testID?: string
|
|
||||||
icon?: {
|
|
||||||
ios: MenuItemCommonProps['ios']
|
|
||||||
android: string
|
|
||||||
web: IconProp
|
|
||||||
}
|
|
||||||
}
|
|
||||||
type Props = {
|
|
||||||
items: DropdownItem[]
|
|
||||||
testID?: string
|
|
||||||
accessibilityLabel?: string
|
|
||||||
accessibilityHint?: string
|
|
||||||
triggerStyle?: ViewStyle
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated use Menu from `#/components/Menu.tsx` instead
|
|
||||||
*/
|
|
||||||
export function NativeDropdown({
|
|
||||||
items,
|
|
||||||
children,
|
|
||||||
testID,
|
|
||||||
accessibilityLabel,
|
|
||||||
accessibilityHint,
|
|
||||||
triggerStyle,
|
|
||||||
}: React.PropsWithChildren<Props>) {
|
|
||||||
const [open, setOpen] = React.useState(false)
|
|
||||||
const buttonRef = React.useRef<HTMLButtonElement>(null)
|
|
||||||
const menuRef = React.useRef<HTMLDivElement>(null)
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (!open) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
function clickHandler(e: MouseEvent) {
|
|
||||||
const t = e.target
|
|
||||||
|
|
||||||
if (!open) return
|
|
||||||
if (!t) return
|
|
||||||
if (!buttonRef.current || !menuRef.current) return
|
|
||||||
|
|
||||||
if (
|
|
||||||
t !== buttonRef.current &&
|
|
||||||
!buttonRef.current.contains(t as Node) &&
|
|
||||||
t !== menuRef.current &&
|
|
||||||
!menuRef.current.contains(t as Node)
|
|
||||||
) {
|
|
||||||
// prevent clicking through to links beneath dropdown
|
|
||||||
// only applies to mobile web
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
|
|
||||||
// close menu
|
|
||||||
setOpen(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function keydownHandler(e: KeyboardEvent) {
|
|
||||||
if (e.key === 'Escape' && open) {
|
|
||||||
setOpen(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener('click', clickHandler, true)
|
|
||||||
window.addEventListener('keydown', keydownHandler, true)
|
|
||||||
return () => {
|
|
||||||
document.removeEventListener('click', clickHandler, true)
|
|
||||||
window.removeEventListener('keydown', keydownHandler, true)
|
|
||||||
}
|
|
||||||
}, [open, setOpen])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownMenuRoot open={open} onOpenChange={o => setOpen(o)}>
|
|
||||||
<DropdownMenu.Trigger asChild>
|
|
||||||
<Pressable
|
|
||||||
ref={buttonRef as unknown as React.Ref<View>}
|
|
||||||
testID={testID}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel={accessibilityLabel}
|
|
||||||
accessibilityHint={accessibilityHint}
|
|
||||||
onPointerDown={e => {
|
|
||||||
// Prevent false positive that interpret mobile scroll as a tap.
|
|
||||||
// This requires the custom onPress handler below to compensate.
|
|
||||||
// https://github.com/radix-ui/primitives/issues/1912
|
|
||||||
e.preventDefault()
|
|
||||||
}}
|
|
||||||
onPress={() => {
|
|
||||||
if (window.event instanceof KeyboardEvent) {
|
|
||||||
// The onPointerDown hack above is not relevant to this press, so don't do anything.
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// Compensate for the disabled onPointerDown above by triggering it manually.
|
|
||||||
setOpen(o => !o)
|
|
||||||
}}
|
|
||||||
hitSlop={HITSLOP_10}
|
|
||||||
style={triggerStyle}>
|
|
||||||
{children}
|
|
||||||
</Pressable>
|
|
||||||
</DropdownMenu.Trigger>
|
|
||||||
|
|
||||||
<DropdownMenu.Portal>
|
|
||||||
<DropdownContent items={items} menuRef={menuRef} />
|
|
||||||
</DropdownMenu.Portal>
|
|
||||||
</DropdownMenuRoot>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function DropdownContent({
|
|
||||||
items,
|
|
||||||
menuRef,
|
|
||||||
}: {
|
|
||||||
items: DropdownItem[]
|
|
||||||
menuRef: React.RefObject<HTMLDivElement | null>
|
|
||||||
}) {
|
|
||||||
const pal = usePalette('default')
|
|
||||||
const theme = useTheme()
|
|
||||||
const dropDownBackgroundColor =
|
|
||||||
theme.colorScheme === 'dark' ? pal.btn : pal.view
|
|
||||||
const {borderColor: separatorColor} =
|
|
||||||
theme.colorScheme === 'dark' ? pal.borderDark : pal.border
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DropdownMenu.Content
|
|
||||||
ref={menuRef}
|
|
||||||
style={
|
|
||||||
StyleSheet.flatten([
|
|
||||||
styles.content,
|
|
||||||
dropDownBackgroundColor,
|
|
||||||
]) as React.CSSProperties
|
|
||||||
}
|
|
||||||
loop>
|
|
||||||
{items.map((item, index) => {
|
|
||||||
if (item.label === 'separator') {
|
|
||||||
return (
|
|
||||||
<DropdownMenu.Separator
|
|
||||||
key={getKey(item.label, index, item.testID)}
|
|
||||||
style={
|
|
||||||
StyleSheet.flatten([
|
|
||||||
styles.separator,
|
|
||||||
{backgroundColor: separatorColor},
|
|
||||||
]) as React.CSSProperties
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (index > 1 && items[index - 1].label === 'separator') {
|
|
||||||
return (
|
|
||||||
<DropdownMenu.Group key={getKey(item.label, index, item.testID)}>
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={getKey(item.label, index, item.testID)}
|
|
||||||
onSelect={item.onPress}>
|
|
||||||
<Text selectable={false} style={[pal.text, styles.itemTitle]}>
|
|
||||||
{item.label}
|
|
||||||
</Text>
|
|
||||||
{item.icon && (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={item.icon.web}
|
|
||||||
size={20}
|
|
||||||
color={pal.colors.textLight}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenu.Group>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<DropdownMenuItem
|
|
||||||
key={getKey(item.label, index, item.testID)}
|
|
||||||
onSelect={item.onPress}>
|
|
||||||
<Text selectable={false} style={[pal.text, styles.itemTitle]}>
|
|
||||||
{item.label}
|
|
||||||
</Text>
|
|
||||||
{item.icon && (
|
|
||||||
<FontAwesomeIcon
|
|
||||||
icon={item.icon.web}
|
|
||||||
size={20}
|
|
||||||
color={pal.colors.textLight}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</DropdownMenuItem>
|
|
||||||
)
|
|
||||||
})}
|
|
||||||
</DropdownMenu.Content>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const getKey = (label: string, index: number, id?: string) => {
|
|
||||||
if (id) {
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
return `${label}_${index}`
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
separator: {
|
|
||||||
height: 1,
|
|
||||||
marginTop: 4,
|
|
||||||
marginBottom: 4,
|
|
||||||
},
|
|
||||||
content: {
|
|
||||||
backgroundColor: '#f0f0f0',
|
|
||||||
borderRadius: 8,
|
|
||||||
paddingTop: 4,
|
|
||||||
paddingBottom: 4,
|
|
||||||
paddingLeft: 4,
|
|
||||||
paddingRight: 4,
|
|
||||||
marginTop: 6,
|
|
||||||
|
|
||||||
// @ts-ignore web only -prf
|
|
||||||
boxShadow: 'rgba(0, 0, 0, 0.3) 0px 5px 20px',
|
|
||||||
},
|
|
||||||
item: {
|
|
||||||
display: 'flex',
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
columnGap: 20,
|
|
||||||
cursor: 'pointer',
|
|
||||||
paddingTop: 8,
|
|
||||||
paddingBottom: 8,
|
|
||||||
paddingLeft: 12,
|
|
||||||
paddingRight: 12,
|
|
||||||
borderRadius: 8,
|
|
||||||
fontFamily:
|
|
||||||
'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Liberation Sans", Helvetica, Arial, sans-serif',
|
|
||||||
// @ts-expect-error web only
|
|
||||||
outline: 0,
|
|
||||||
border: 0,
|
|
||||||
},
|
|
||||||
itemTitle: {
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: '600',
|
|
||||||
paddingRight: 10,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
Reference in New Issue
Block a user