enable react native strict types
This commit is contained in:
@@ -56,6 +56,16 @@ jest.mock('expo-media-library', () => ({
|
||||
__esModule: true, // this property makes it work
|
||||
default: jest.fn(),
|
||||
usePermissions: jest.fn(() => [true]),
|
||||
requestPermissionsAsync: jest.fn().mockResolvedValue({granted: true}),
|
||||
saveToLibraryAsync: jest.fn().mockResolvedValue(undefined),
|
||||
}))
|
||||
|
||||
jest.mock('expo-media-library/legacy', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
usePermissions: jest.fn(() => [true]),
|
||||
requestPermissionsAsync: jest.fn().mockResolvedValue({granted: true}),
|
||||
saveToLibraryAsync: jest.fn().mockResolvedValue(undefined),
|
||||
}))
|
||||
|
||||
jest.mock('@bsky.app/expo-guess-language', () => ({
|
||||
|
||||
+15
-167
@@ -80,14 +80,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/alf/util/flatten.ts": {
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-unsafe-member-access": {
|
||||
"count": 9
|
||||
}
|
||||
},
|
||||
"src/alf/util/systemUI.ts": {
|
||||
"typescript/no-floating-promises": {
|
||||
"count": 2
|
||||
@@ -167,9 +159,6 @@
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 4
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-unsafe-call": {
|
||||
"count": 2
|
||||
},
|
||||
@@ -224,9 +213,6 @@
|
||||
"src/components/Lists.tsx": {
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/Menu/types.ts": {
|
||||
@@ -261,9 +247,6 @@
|
||||
"src/components/PostControls/BookmarkButton.tsx": {
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 2
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/PostControls/DiscoverDebug.tsx": {
|
||||
@@ -335,9 +318,6 @@
|
||||
"typescript/no-floating-promises": {
|
||||
"count": 5
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 2
|
||||
},
|
||||
"typescript/require-await": {
|
||||
"count": 4
|
||||
}
|
||||
@@ -346,9 +326,6 @@
|
||||
"typescript/no-floating-promises": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 2
|
||||
},
|
||||
"typescript/require-await": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -363,16 +340,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/ageAssurance/AgeAssuranceErrors.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/ageAssurance/AgeAssuranceInitDialog.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/components/ageAssurance/AgeAssuranceRedirectDialog.tsx": {
|
||||
"typescript/require-await": {
|
||||
"count": 1
|
||||
@@ -388,18 +355,10 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/contacts/screens/ViewMatches.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/dialogs/DeviceLocationRequestDialog.tsx": {
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-unsafe-member-access": {
|
||||
"count": 2
|
||||
}
|
||||
@@ -409,26 +368,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/dialogs/EmailDialog/screens/Manage2FA/Disable.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"src/components/dialogs/EmailDialog/screens/Manage2FA/Enable.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/dialogs/EmailDialog/screens/Update.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"src/components/dialogs/EmailDialog/screens/Verify.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"src/components/dialogs/LanguageSelectDialog.tsx": {
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 1
|
||||
@@ -447,7 +386,7 @@
|
||||
"count": 2
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 3
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-unsafe-member-access": {
|
||||
"count": 2
|
||||
@@ -490,9 +429,6 @@
|
||||
"src/components/dialogs/lists/CreateOrEditListDialog.tsx": {
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 2
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/dialogs/nuxs/FindContactsAnnouncement.tsx": {
|
||||
@@ -580,16 +516,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/components/intents/VerifyEmailIntentDialog.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/verification/VerificationCreatePrompt.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/components/verification/VerificationRemovePrompt.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
@@ -696,13 +622,10 @@
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 3
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/lib/hooks/usePermissions.ts": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-unsafe-enum-comparison": {
|
||||
"count": 5
|
||||
}
|
||||
@@ -848,12 +771,12 @@
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-unsafe-member-access": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/screens/E2E/SharedPreferencesTesterScreen.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 6
|
||||
},
|
||||
"typescript/require-await": {
|
||||
"count": 6
|
||||
}
|
||||
@@ -863,22 +786,9 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/screens/Feeds/NoSavedFeedsOfAnyType.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/screens/Home/NoFeedsPinned.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/screens/List/ListHiddenScreen.tsx": {
|
||||
"typescript/no-floating-promises": {
|
||||
"count": 3
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/screens/Log.tsx": {
|
||||
@@ -894,7 +804,7 @@
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-unsafe-member-access": {
|
||||
"count": 2
|
||||
@@ -922,19 +832,6 @@
|
||||
"src/screens/Onboarding/StepProfile/index.tsx": {
|
||||
"typescript/no-floating-promises": {
|
||||
"count": 2
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/screens/Onboarding/StepSuggestedStarterpacks/StarterPackCard.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/screens/Onboarding/StepSuggestedStarterpacks/index.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/screens/PostThread/components/ThreadItemAnchorFollowButton.tsx": {
|
||||
@@ -962,18 +859,12 @@
|
||||
"src/screens/Profile/Header/EditProfileDialog.tsx": {
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 3
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/screens/Profile/Header/ProfileHeaderLabeler.tsx": {
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 2
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-unsafe-member-access": {
|
||||
"count": 3
|
||||
}
|
||||
@@ -1004,11 +895,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/screens/ProfileList/components/Header.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 3
|
||||
}
|
||||
},
|
||||
"src/screens/ProfileList/components/MoreOptionsMenu.tsx": {
|
||||
"typescript/no-floating-promises": {
|
||||
"count": 1
|
||||
@@ -1027,11 +913,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/screens/SavedFeeds.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/screens/Search/modules/ExploreSuggestedAccounts.tsx": {
|
||||
"typescript/no-floating-promises": {
|
||||
"count": 1
|
||||
@@ -1096,6 +977,12 @@
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 2
|
||||
},
|
||||
"typescript/no-unsafe-call": {
|
||||
"count": 2
|
||||
},
|
||||
"typescript/no-unsafe-member-access": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/screens/Settings/components/CopyButton.tsx": {
|
||||
@@ -1107,9 +994,6 @@
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-unsafe-member-access": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -1119,11 +1003,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/screens/Settings/components/DisableEmail2FADialog.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"src/screens/Settings/components/OTAInfo.tsx": {
|
||||
"typescript/no-floating-promises": {
|
||||
"count": 1
|
||||
@@ -1147,7 +1026,7 @@
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 2
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-unsafe-call": {
|
||||
"count": 1
|
||||
@@ -1168,9 +1047,6 @@
|
||||
"typescript/no-floating-promises": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 2
|
||||
},
|
||||
"typescript/require-await": {
|
||||
"count": 1
|
||||
}
|
||||
@@ -1474,9 +1350,6 @@
|
||||
"src/view/com/composer/SelectMediaButton.tsx": {
|
||||
"typescript/no-floating-promises": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/view/com/composer/drafts/DraftsButton.tsx": {
|
||||
@@ -1500,11 +1373,6 @@
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/view/com/composer/photos/EditImageDialog.web.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/view/com/composer/photos/Gallery.tsx": {
|
||||
"typescript/no-floating-promises": {
|
||||
"count": 1
|
||||
@@ -1513,9 +1381,6 @@
|
||||
"src/view/com/composer/photos/OpenCameraBtn.tsx": {
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/view/com/composer/text-input/TextInput.web.tsx": {
|
||||
@@ -1560,7 +1425,7 @@
|
||||
"count": 2
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 3
|
||||
"count": 2
|
||||
},
|
||||
"typescript/no-unsafe-member-access": {
|
||||
"count": 6
|
||||
@@ -1598,12 +1463,6 @@
|
||||
"src/view/com/pager/TabBar.web.tsx": {
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-unsafe-call": {
|
||||
"count": 2
|
||||
},
|
||||
"typescript/no-unsafe-member-access": {
|
||||
"count": 4
|
||||
}
|
||||
},
|
||||
"src/view/com/post-thread/PostLikedBy.tsx": {
|
||||
@@ -1627,9 +1486,6 @@
|
||||
"src/view/com/posts/FeedShutdownMsg.tsx": {
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 2
|
||||
},
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 2
|
||||
}
|
||||
},
|
||||
"src/view/com/posts/PostFeedErrorMessage.tsx": {
|
||||
@@ -1693,9 +1549,6 @@
|
||||
"src/view/com/util/Views.tsx": {
|
||||
"react/display-name": {
|
||||
"count": 1
|
||||
},
|
||||
"typescript/no-explicit-any": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/view/com/util/Views.web.tsx": {
|
||||
@@ -1703,11 +1556,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/view/com/util/forms/Button.tsx": {
|
||||
"typescript/no-misused-promises": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/view/screens/Feeds.tsx": {
|
||||
"typescript/no-floating-promises": {
|
||||
"count": 3
|
||||
@@ -1771,4 +1619,4 @@
|
||||
"count": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+3
-2
@@ -24,10 +24,11 @@ import {atoms as a} from '#/alf'
|
||||
import splashImagePointer from '../assets/splash/splash.png'
|
||||
// @ts-ignore
|
||||
import darkSplashImagePointer from '../assets/splash/splash-dark.png'
|
||||
const splashImageUri = RNImage.resolveAssetSource(splashImagePointer).uri
|
||||
|
||||
const splashImageUri = RNImage.resolveAssetSource(splashImagePointer)!.uri
|
||||
const darkSplashImageUri = RNImage.resolveAssetSource(
|
||||
darkSplashImagePointer,
|
||||
).uri
|
||||
)!.uri
|
||||
|
||||
export const Logo = forwardRef(function LogoImpl(props: SvgProps, ref) {
|
||||
const width = 1000
|
||||
|
||||
+2
-2
@@ -7,7 +7,7 @@
|
||||
import {useEffect, useRef, useState} from 'react'
|
||||
import Svg, {Path} from 'react-native-svg'
|
||||
|
||||
import {atoms as a, flatten} from '#/alf'
|
||||
import {atoms as a, flattenToCSS} from '#/alf'
|
||||
|
||||
const size = 100
|
||||
const ratio = 57 / 64
|
||||
@@ -72,7 +72,7 @@ export function Splash({
|
||||
{!isAnimationComplete && (
|
||||
<div
|
||||
ref={splashRef}
|
||||
style={flatten([
|
||||
style={flattenToCSS([
|
||||
a.fixed,
|
||||
a.inset_0,
|
||||
a.flex,
|
||||
|
||||
+6
-1
@@ -3,6 +3,8 @@ import {type FontVariant, type TextStyle} from 'react-native'
|
||||
import {IS_ANDROID, IS_WEB} from '#/env'
|
||||
import {type Device, device} from '#/storage'
|
||||
|
||||
export type MutableTextStyle = {-readonly [K in keyof TextStyle]: TextStyle[K]}
|
||||
|
||||
const WEB_FONT_FAMILIES = `system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"`
|
||||
|
||||
const factor = 0.0625 // 1 - (15/16)
|
||||
@@ -37,7 +39,10 @@ export function setFontFamily(fontFamily: Device['fontFamily']) {
|
||||
/*
|
||||
* Unused fonts are commented out, but the files are there if we need them.
|
||||
*/
|
||||
export function applyFonts(style: TextStyle, fontFamily: 'system' | 'theme') {
|
||||
export function applyFonts(
|
||||
style: MutableTextStyle,
|
||||
fontFamily: 'system' | 'theme',
|
||||
) {
|
||||
if (fontFamily === 'theme') {
|
||||
if (IS_ANDROID) {
|
||||
style.fontFamily =
|
||||
|
||||
@@ -7,7 +7,13 @@ import {
|
||||
import {UITextView} from '@bsky.app/react-native-uitextview'
|
||||
import createEmojiRegex from 'emoji-regex'
|
||||
|
||||
import {type Alf, applyFonts, atoms, flatten} from '#/alf'
|
||||
import {
|
||||
type Alf,
|
||||
applyFonts,
|
||||
atoms,
|
||||
flatten,
|
||||
type MutableTextStyle,
|
||||
} from '#/alf'
|
||||
import {IS_IOS, IS_NATIVE} from '#/env'
|
||||
|
||||
/**
|
||||
@@ -27,7 +33,7 @@ export function normalizeTextStyles(
|
||||
fontFamily: Alf['fonts']['family']
|
||||
} & Pick<Alf, 'flags'>,
|
||||
) {
|
||||
const s = flatten(styles) ?? {}
|
||||
const s: MutableTextStyle = {...flatten(styles)}
|
||||
|
||||
// should always be defined on these components
|
||||
s.fontSize = (s.fontSize || atoms.text_md.fontSize) * fontScale
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {useEffect, useState} from 'react'
|
||||
import {Dimensions} from 'react-native'
|
||||
import {Dimensions, type DimensionsPayload} from 'react-native'
|
||||
|
||||
/**
|
||||
* Same as `useWindowDimensions().fontScale`, but avoids rerendering
|
||||
@@ -9,9 +9,12 @@ export function useNativeFontScale() {
|
||||
const [fontScale, setFontScale] = useState(Dimensions.get('window').fontScale)
|
||||
|
||||
useEffect(() => {
|
||||
const sub = Dimensions.addEventListener('change', evt => {
|
||||
setFontScale(evt.window.fontScale)
|
||||
})
|
||||
const sub = Dimensions.addEventListener(
|
||||
'change',
|
||||
(evt: DimensionsPayload) => {
|
||||
if (evt.window) setFontScale(evt.window.fontScale)
|
||||
},
|
||||
)
|
||||
return () => sub.remove()
|
||||
}, [])
|
||||
|
||||
|
||||
+16
-3
@@ -1,6 +1,19 @@
|
||||
import {type DimensionValue, StyleSheet} from 'react-native'
|
||||
import {type DimensionValue, type StyleProp, StyleSheet} from 'react-native'
|
||||
|
||||
export const flatten = StyleSheet.flatten
|
||||
export function flatten<T>(
|
||||
style?: StyleProp<T>,
|
||||
): T extends (infer U)[] ? U : T {
|
||||
return (StyleSheet.flatten(
|
||||
style as unknown as Parameters<typeof StyleSheet.flatten>[0],
|
||||
) ?? {}) as T extends (infer U)[] ? U : T
|
||||
}
|
||||
|
||||
/** Flatten React Native styles passed directly to a web-only DOM component. */
|
||||
export function flattenToCSS(style: unknown): React.CSSProperties {
|
||||
return (StyleSheet.flatten(
|
||||
style as Parameters<typeof StyleSheet.flatten>[0],
|
||||
) ?? {}) as React.CSSProperties
|
||||
}
|
||||
|
||||
/**
|
||||
* Coerce a style value to a number. Padding values are typed as
|
||||
@@ -28,7 +41,7 @@ interface PaddingStyle {
|
||||
* non-numeric `DimensionValue` (e.g. percentages) is treated as 0.
|
||||
*/
|
||||
export function extractPadding(style: PaddingStyle | PaddingStyle[]) {
|
||||
const s = flatten(style as any) ?? {}
|
||||
const s = flatten(style)
|
||||
const base = num(s.padding)
|
||||
return {
|
||||
paddingTop: num(s.paddingTop) || num(s.paddingVertical) || base,
|
||||
|
||||
@@ -24,7 +24,7 @@ export function useThemeName(): ThemeName {
|
||||
}
|
||||
|
||||
function getThemeName(
|
||||
colorScheme: ColorSchemeName,
|
||||
colorScheme: ColorSchemeName | null | undefined,
|
||||
colorMode: 'system' | 'light' | 'dark',
|
||||
darkTheme?: ThemeName,
|
||||
) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {Pressable} from 'react-native'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {HITSLOP_20} from '#/lib/constants'
|
||||
import {useLargeAltBadgeEnabled} from '#/state/preferences/large-alt-badge'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Pressable} from '#/components/Pressable'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
@@ -49,7 +49,7 @@ export function AltBadgeWithDialog({
|
||||
accessibilityHint=""
|
||||
hitSlop={HITSLOP_20}
|
||||
onPress={control.open}
|
||||
style={s => [
|
||||
style={({pressed, hovered}) => [
|
||||
a.justify_center,
|
||||
a.rounded_sm,
|
||||
a.p_xs,
|
||||
@@ -62,7 +62,7 @@ export function AltBadgeWithDialog({
|
||||
opacity: 0.8,
|
||||
},
|
||||
pos,
|
||||
s.hovered || s.pressed
|
||||
hovered || pressed
|
||||
? [
|
||||
{
|
||||
opacity: 1,
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import {useState} from 'react'
|
||||
import {type Insets, Pressable, View} from 'react-native'
|
||||
import {type Insets, View} from 'react-native'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
import {usePreferencesQuery} from '#/state/queries/preferences'
|
||||
import {useSession} from '#/state/session'
|
||||
import {atoms as a, useTheme} from '#/alf'
|
||||
import {Beaker_Stroke2_Corner2_Rounded as BeakerIcon} from '#/components/icons/Beaker'
|
||||
import {Pressable} from '#/components/Pressable'
|
||||
import * as Tooltip from '#/components/Tooltip'
|
||||
import type * as bsky from '#/types/bsky'
|
||||
|
||||
|
||||
@@ -101,6 +101,8 @@ export type ButtonProps = Pick<
|
||||
| 'onPressOut'
|
||||
| 'onFocus'
|
||||
| 'onBlur'
|
||||
| 'onAccessibilityAction'
|
||||
| 'onAccessibilityEscape'
|
||||
> &
|
||||
AccessibilityProps &
|
||||
VariantProps & {
|
||||
@@ -131,7 +133,7 @@ export function useButtonContext() {
|
||||
return useContext(Context)
|
||||
}
|
||||
|
||||
export const Button = forwardRef<View, ButtonProps>(
|
||||
export const Button = forwardRef<React.ComponentRef<typeof View>, ButtonProps>(
|
||||
(
|
||||
{
|
||||
children,
|
||||
|
||||
@@ -39,6 +39,9 @@ import {
|
||||
import {Span, Text} from '#/components/Typography'
|
||||
import {IS_IOS, IS_WEB, IS_WEB_TOUCH_DEVICE} from '#/env'
|
||||
|
||||
type TextInputInstance = React.ComponentRef<typeof TextInput>
|
||||
type ViewInstance = React.ComponentRef<typeof View>
|
||||
|
||||
export type SubmitRequest =
|
||||
| {
|
||||
platform: 'web'
|
||||
@@ -60,7 +63,7 @@ export type ComposerInternalApi = {
|
||||
input?: ReturnType<typeof useTapper>['input']
|
||||
clear: () => void
|
||||
insert(text: string): void
|
||||
setAutocompleteAnchor: (node: View | null) => void
|
||||
setAutocompleteAnchor: (node: ViewInstance | null) => void
|
||||
}
|
||||
|
||||
export function useComposerInternalApiRef() {
|
||||
@@ -82,7 +85,7 @@ export type ComposerProps = Omit<
|
||||
| 'onSubmitEditing'
|
||||
> & {
|
||||
label: string
|
||||
ref?: React.RefObject<TextInput>
|
||||
ref?: React.RefObject<TextInputInstance>
|
||||
internalApiRef?: React.Ref<ComposerInternalApi>
|
||||
outerStyle?: ViewStyleProp['style']
|
||||
contentTextStyle?: TextStyleProp['style']
|
||||
@@ -139,6 +142,11 @@ export function Composer({
|
||||
placement: autocompletePlacement,
|
||||
dynamicWidth: IS_WEB,
|
||||
})
|
||||
const inputRef = mergeRefs<TextInputInstance>([
|
||||
ref,
|
||||
tapper.inputProps.ref as React.Ref<TextInputInstance>,
|
||||
sift.targetProps.ref as React.Ref<TextInputInstance>,
|
||||
])
|
||||
|
||||
/*
|
||||
* Active facet state for controlling the visibility of the Autocomplete.
|
||||
@@ -345,7 +353,7 @@ export function Composer({
|
||||
{...rest}
|
||||
{...tapper.inputProps}
|
||||
{...sift.targetProps}
|
||||
ref={mergeRefs([ref, tapper.inputProps.ref, sift.targetProps.ref])}
|
||||
ref={inputRef}
|
||||
rawValue={tapper.state.text}
|
||||
onBlur={e => {
|
||||
rest.onBlur?.(e)
|
||||
|
||||
@@ -244,7 +244,7 @@ export function Trigger({
|
||||
const context = useContextMenuContext()
|
||||
const playHaptic = useHaptics()
|
||||
const insets = useSafeAreaInsets()
|
||||
const ref = useRef<View>(null)
|
||||
const ref = useRef<React.ComponentRef<typeof View>>(null)
|
||||
const isFocused = useIsFocused()
|
||||
const [image, setImage] = useState<string | null>(null)
|
||||
const [pendingMeasurement, setPendingMeasurement] = useState<{
|
||||
@@ -971,7 +971,10 @@ export function Divider() {
|
||||
)
|
||||
}
|
||||
|
||||
function measureView(view: View | null, insets: EdgeInsets) {
|
||||
function measureView(
|
||||
view: React.ComponentRef<typeof View> | null,
|
||||
insets: EdgeInsets,
|
||||
) {
|
||||
if (!view) return Promise.resolve(null)
|
||||
return new Promise<Measurement>(resolve => {
|
||||
view?.measureInWindow((x, y, width, height) =>
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from 'react'
|
||||
import {
|
||||
Keyboard,
|
||||
type KeyboardEventListener,
|
||||
type KeyboardEvent,
|
||||
type LayoutChangeEvent,
|
||||
type NativeScrollEvent,
|
||||
type NativeSyntheticEvent,
|
||||
@@ -203,80 +203,85 @@ export function Inner(props: DialogInnerProps) {
|
||||
return <ScrollableInner {...props} />
|
||||
}
|
||||
|
||||
export const ScrollableInner = forwardRef<ScrollView, DialogInnerProps>(
|
||||
function ScrollableInner(
|
||||
{children, contentContainerStyle, header, footer, style, ...props},
|
||||
ref,
|
||||
) {
|
||||
const {nativeSnapPoint, disableDrag, setDisableDrag, isHeightConstrained} =
|
||||
useDialogContext()
|
||||
const isAtMaxSnapPoint = nativeSnapPoint === BottomSheetSnapPoint.Full
|
||||
const insets = useSafeAreaInsets()
|
||||
const [keyboardHeight, setKeyboardHeight] = useState(() =>
|
||||
IS_ANDROID ? (Keyboard.metrics()?.height ?? 0) : 0,
|
||||
)
|
||||
export function ScrollableInner({
|
||||
ref,
|
||||
children,
|
||||
contentContainerStyle,
|
||||
header,
|
||||
footer,
|
||||
style,
|
||||
...props
|
||||
}: DialogInnerProps & {
|
||||
ref?: React.Ref<React.ComponentRef<typeof ScrollView>>
|
||||
}) {
|
||||
const {nativeSnapPoint, disableDrag, setDisableDrag, isHeightConstrained} =
|
||||
useDialogContext()
|
||||
const isAtMaxSnapPoint = nativeSnapPoint === BottomSheetSnapPoint.Full
|
||||
const insets = useSafeAreaInsets()
|
||||
const [keyboardHeight, setKeyboardHeight] = useState(() =>
|
||||
IS_ANDROID ? (Keyboard.metrics()?.height ?? 0) : 0,
|
||||
)
|
||||
|
||||
const keyboardEventHandler = useCallback<KeyboardEventListener>(e => {
|
||||
setKeyboardHeight(e.endCoordinates.height)
|
||||
}, [])
|
||||
useOnKeyboard('keyboardDidShow', keyboardEventHandler)
|
||||
useOnKeyboard('keyboardDidHide', keyboardEventHandler)
|
||||
const keyboardEventHandler = useCallback((e: KeyboardEvent) => {
|
||||
setKeyboardHeight(e.endCoordinates.height)
|
||||
}, [])
|
||||
useOnKeyboard('keyboardDidShow', keyboardEventHandler)
|
||||
useOnKeyboard('keyboardDidHide', keyboardEventHandler)
|
||||
|
||||
const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
if (!IS_ANDROID) {
|
||||
return
|
||||
}
|
||||
const {contentOffset} = e.nativeEvent
|
||||
if (contentOffset.y > 0 && !disableDrag) {
|
||||
setDisableDrag(true)
|
||||
} else if (contentOffset.y <= 1 && disableDrag) {
|
||||
setDisableDrag(false)
|
||||
}
|
||||
const onScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
if (!IS_ANDROID) {
|
||||
return
|
||||
}
|
||||
const {contentOffset} = e.nativeEvent
|
||||
if (contentOffset.y > 0 && !disableDrag) {
|
||||
setDisableDrag(true)
|
||||
} else if (contentOffset.y <= 1 && disableDrag) {
|
||||
setDisableDrag(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<ScrollView
|
||||
style={[isHeightConstrained && a.flex_1, style]}
|
||||
contentContainerStyle={[
|
||||
a.pt_2xl,
|
||||
IS_LIQUID_GLASS ? a.px_2xl : a.px_xl,
|
||||
platform({
|
||||
ios: a.pb_2xl,
|
||||
android: {
|
||||
paddingBottom: keyboardHeight + insets.bottom + tokens.space.xl,
|
||||
},
|
||||
}),
|
||||
contentContainerStyle,
|
||||
]}
|
||||
ref={ref}
|
||||
showsVerticalScrollIndicator={IS_ANDROID ? false : undefined}
|
||||
contentInsetAdjustmentBehavior={
|
||||
isAtMaxSnapPoint ? 'automatic' : 'never'
|
||||
}
|
||||
automaticallyAdjustKeyboardInsets={isAtMaxSnapPoint}
|
||||
{...props}
|
||||
bounces={isAtMaxSnapPoint}
|
||||
scrollEventThrottle={50}
|
||||
// set drag state based on scroll on android.
|
||||
// we want to detect if it's at the top or not, so watch
|
||||
// scrollEndDrag and momentumScrollEnd as well
|
||||
onScroll={android(onScroll)}
|
||||
onScrollEndDrag={android(onScroll)}
|
||||
onMomentumScrollEnd={android(onScroll)}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
// TODO: figure out why this positions the header absolutely (rather than stickily)
|
||||
// on Android. fine to disable for now, because we don't have any
|
||||
// dialogs that use this that actually scroll -sfn
|
||||
stickyHeaderIndices={ios(header ? [0] : undefined)}>
|
||||
{header}
|
||||
{children}
|
||||
</ScrollView>
|
||||
{footer}
|
||||
</>
|
||||
)
|
||||
},
|
||||
)
|
||||
return (
|
||||
<>
|
||||
<ScrollView
|
||||
style={[isHeightConstrained && a.flex_1, style]}
|
||||
contentContainerStyle={[
|
||||
a.pt_2xl,
|
||||
IS_LIQUID_GLASS ? a.px_2xl : a.px_xl,
|
||||
platform({
|
||||
ios: a.pb_2xl,
|
||||
android: {
|
||||
paddingBottom: keyboardHeight + insets.bottom + tokens.space.xl,
|
||||
},
|
||||
}),
|
||||
contentContainerStyle,
|
||||
]}
|
||||
ref={ref}
|
||||
showsVerticalScrollIndicator={IS_ANDROID ? false : undefined}
|
||||
contentInsetAdjustmentBehavior={
|
||||
isAtMaxSnapPoint ? 'automatic' : 'never'
|
||||
}
|
||||
automaticallyAdjustKeyboardInsets={isAtMaxSnapPoint}
|
||||
{...props}
|
||||
bounces={isAtMaxSnapPoint}
|
||||
scrollEventThrottle={50}
|
||||
// set drag state based on scroll on android.
|
||||
// we want to detect if it's at the top or not, so watch
|
||||
// scrollEndDrag and momentumScrollEnd as well
|
||||
onScroll={android(onScroll)}
|
||||
onScrollEndDrag={android(onScroll)}
|
||||
onMomentumScrollEnd={android(onScroll)}
|
||||
keyboardShouldPersistTaps="handled"
|
||||
// TODO: figure out why this positions the header absolutely (rather than stickily)
|
||||
// on Android. fine to disable for now, because we don't have any
|
||||
// dialogs that use this that actually scroll -sfn
|
||||
stickyHeaderIndices={ios(header ? [0] : undefined)}>
|
||||
{header}
|
||||
{children}
|
||||
</ScrollView>
|
||||
{footer}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export const InnerFlatList = forwardRef<
|
||||
ListMethods,
|
||||
|
||||
@@ -193,7 +193,6 @@ export function Inner({
|
||||
aria-label={label}
|
||||
aria-labelledby={accessibilityLabelledBy}
|
||||
aria-describedby={accessibilityDescribedBy}
|
||||
// @ts-expect-error web only -prf
|
||||
onClick={stopPropagation}
|
||||
onStartShouldSetResponder={_ => true}
|
||||
onTouchEnd={stopPropagation}
|
||||
@@ -239,7 +238,9 @@ export function Inner({
|
||||
export function ScrollableInner({
|
||||
ref: _ref,
|
||||
...props
|
||||
}: DialogInnerProps & {ref?: React.Ref<ScrollView>}) {
|
||||
}: DialogInnerProps & {
|
||||
ref?: React.Ref<React.ComponentRef<typeof ScrollView>>
|
||||
}) {
|
||||
return <Inner {...props} />
|
||||
}
|
||||
|
||||
|
||||
@@ -217,7 +217,7 @@ export function ProfileGrid({
|
||||
|
||||
// Track seen profiles
|
||||
const seenProfilesRef = useRef<Set<string>>(new Set())
|
||||
const containerRef = useRef<View>(null)
|
||||
const containerRef = useRef<React.ComponentRef<typeof View>>(null)
|
||||
const hasTrackedRef = useRef(false)
|
||||
const logContext: Metrics['suggestedUser:seen']['logContext'] = isFeedContext
|
||||
? 'DiscoverInterstitial'
|
||||
|
||||
@@ -41,7 +41,7 @@ export function FocusScope({children}: {children: React.ReactNode}) {
|
||||
*/
|
||||
function FocusTrap({children}: {children: React.ReactNode}) {
|
||||
const {_} = useLingui()
|
||||
const child = useRef<View>(null)
|
||||
const child = useRef<React.ComponentRef<typeof View>>(null)
|
||||
|
||||
/*
|
||||
* Here we add a ref to the first child of this component. This currently
|
||||
@@ -66,13 +66,16 @@ function FocusTrap({children}: {children: React.ReactNode}) {
|
||||
})
|
||||
}, [children])
|
||||
|
||||
const focusNode = useCallback((ref: View | null) => {
|
||||
if (!ref) return
|
||||
const node = findNodeHandle(ref)
|
||||
if (node) {
|
||||
AccessibilityInfo.setAccessibilityFocus(node)
|
||||
}
|
||||
}, [])
|
||||
const focusNode = useCallback(
|
||||
(ref: React.ComponentRef<typeof View> | null) => {
|
||||
if (!ref) return
|
||||
const node = findNodeHandle(ref)
|
||||
if (node) {
|
||||
AccessibilityInfo.setAccessibilityFocus(node)
|
||||
}
|
||||
},
|
||||
[],
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
setTimeout(() => {
|
||||
|
||||
@@ -20,7 +20,7 @@ export const IS_GLASS_AVAILABLE =
|
||||
*/
|
||||
export const GlassView = IS_GLASS_AVAILABLE ? InnerGlassView : FallbackView
|
||||
|
||||
export type GlassViewProps = ExpoGlassViewProps & {
|
||||
export type GlassViewProps = Omit<ExpoGlassViewProps, 'ref'> & {
|
||||
fallbackStyle?: StyleProp<ViewStyle>
|
||||
}
|
||||
|
||||
|
||||
@@ -47,7 +47,7 @@ export function InterestTabs({
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const listRef = useRef<ScrollView>(null)
|
||||
const listRef = useRef<React.ComponentRef<typeof ScrollView>>(null)
|
||||
const [totalWidth, setTotalWidth] = useState(0)
|
||||
const [scrollX, setScrollX] = useState(0)
|
||||
const [contentWidth, setContentWidth] = useState(0)
|
||||
|
||||
@@ -42,7 +42,7 @@ export function Outer({
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
noBottomBorder?: boolean
|
||||
headerRef?: React.RefObject<View | null>
|
||||
headerRef?: React.RefObject<React.ComponentRef<typeof View> | null>
|
||||
sticky?: boolean
|
||||
}) {
|
||||
const t = useTheme()
|
||||
|
||||
@@ -35,7 +35,7 @@ const TIMING_OUT = {duration: 150}
|
||||
|
||||
export function ImageMenu({onPressShare, onPressSave}: Props) {
|
||||
const {t: l} = useLingui()
|
||||
const triggerRef = useRef<View>(null)
|
||||
const triggerRef = useRef<React.ComponentRef<typeof View>>(null)
|
||||
const [isMounted, setIsMounted] = useState(false)
|
||||
const [anchor, setAnchor] = useState<Anchor | null>(null)
|
||||
const progress = useSharedValue(0)
|
||||
|
||||
@@ -82,7 +82,7 @@ const ImageItem = ({
|
||||
const scrollHandler = useAnimatedScrollHandler({
|
||||
onScroll(e) {
|
||||
'worklet'
|
||||
const nextIsScaled = e.zoomScale > 1
|
||||
const nextIsScaled = (e.zoomScale ?? 1) > 1
|
||||
if (scaled !== nextIsScaled) {
|
||||
scheduleOnRN(handleZoom, nextIsScaled)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,10 @@ import {useCallback, useEffect, useMemo, useState} from 'react'
|
||||
import {PixelRatio, StyleSheet, useWindowDimensions, View} from 'react-native'
|
||||
import {SystemBars} from 'react-native-edge-to-edge'
|
||||
import {Gesture} from 'react-native-gesture-handler'
|
||||
import PagerView from 'react-native-pager-view'
|
||||
import PagerView, {
|
||||
type PagerViewOnPageSelectedEvent,
|
||||
type PageScrollStateChangedNativeEvent,
|
||||
} from 'react-native-pager-view'
|
||||
import Animated, {
|
||||
type AnimatableValue,
|
||||
type AnimatedRef,
|
||||
@@ -383,7 +386,7 @@ function ImageView({
|
||||
<PagerView
|
||||
scrollEnabled={!isScaled}
|
||||
initialPage={initialImageIndex}
|
||||
onPageSelected={e => {
|
||||
onPageSelected={(e: PagerViewOnPageSelectedEvent) => {
|
||||
const next = e.nativeEvent.position
|
||||
setImageIndex(prev => {
|
||||
if (metricsContext && prev !== next) {
|
||||
@@ -401,7 +404,7 @@ function ImageView({
|
||||
})
|
||||
setIsScaled(false)
|
||||
}}
|
||||
onPageScrollStateChanged={e => {
|
||||
onPageScrollStateChanged={(e: PageScrollStateChangedNativeEvent) => {
|
||||
setIsDragging(e.nativeEvent.pageScrollState !== 'idle')
|
||||
}}
|
||||
overdrag={true}
|
||||
|
||||
@@ -478,7 +478,7 @@ export function InlineLinkText({
|
||||
onIn: onInteract,
|
||||
onOut: onInteractOut,
|
||||
} = useInteractionState()
|
||||
const flattenedStyle = flatten(style) || {}
|
||||
const flattenedStyle = flatten(style)
|
||||
|
||||
return (
|
||||
<Text
|
||||
@@ -570,7 +570,7 @@ export function SimpleInlineLinkText({
|
||||
onIn: onInteract,
|
||||
onOut: onInteractOut,
|
||||
} = useInteractionState()
|
||||
const flattenedStyle = flatten(style) || {}
|
||||
const flattenedStyle = flatten(style)
|
||||
const isExternal = isExternalUrl(to)
|
||||
|
||||
let href = to
|
||||
|
||||
@@ -11,7 +11,7 @@ import {useLingui} from '@lingui/react'
|
||||
import {DropdownMenu} from 'radix-ui'
|
||||
|
||||
import {useA11y} from '#/state/a11y'
|
||||
import {atoms as a, flatten, useTheme, web} from '#/alf'
|
||||
import {atoms as a, flatten, flattenToCSS, useTheme, web} from '#/alf'
|
||||
import type * as Dialog from '#/components/Dialog'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
import {
|
||||
@@ -413,7 +413,7 @@ export function Divider() {
|
||||
const t = useTheme()
|
||||
return (
|
||||
<DropdownMenu.Separator
|
||||
style={flatten([
|
||||
style={flattenToCSS([
|
||||
a.my_xs,
|
||||
t.atoms.bg_contrast_100,
|
||||
a.flex_shrink_0,
|
||||
|
||||
@@ -28,7 +28,7 @@ export type RadixPassThroughTriggerProps = {
|
||||
['aria-controls']?: string
|
||||
['aria-haspopup']?: boolean
|
||||
['aria-expanded']?: AccessibilityProps['aria-expanded']
|
||||
onKeyDown: (e: React.KeyboardEvent) => void
|
||||
onKeyDown: PressableProps['onKeyDown']
|
||||
/**
|
||||
* Radix provides this, but we override on web to use `onPress` instead,
|
||||
* which is less sensitive while scrolling.
|
||||
|
||||
@@ -267,7 +267,7 @@ function TranslationResult({
|
||||
? codeToLanguageName(resultSourceLanguage, i18n.locale)
|
||||
: undefined
|
||||
|
||||
const flattenedStyle = flatten(postTextStyle) ?? {}
|
||||
const flattenedStyle = flatten(postTextStyle)
|
||||
const fontSize = flattenedStyle.fontSize
|
||||
|
||||
return (
|
||||
|
||||
@@ -27,7 +27,7 @@ export function PostControlButton({
|
||||
activeColor,
|
||||
...props
|
||||
}: Omit<ButtonProps, 'hitSlop'> & {
|
||||
ref?: React.Ref<View>
|
||||
ref?: React.Ref<React.ComponentRef<typeof View>>
|
||||
active?: boolean
|
||||
big?: boolean
|
||||
color?: string
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
Pressable as NativePressable,
|
||||
type PressableStateCallbackType as NativePressableStateCallbackType,
|
||||
type StyleProp,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
|
||||
export interface PressableStateCallbackType extends NativePressableStateCallbackType {
|
||||
/** Provided by react-native-web. */
|
||||
readonly focused?: boolean
|
||||
/** Provided by react-native-web. */
|
||||
readonly hovered?: boolean
|
||||
}
|
||||
|
||||
export type PressableProps = Omit<
|
||||
React.ComponentProps<typeof NativePressable>,
|
||||
'children' | 'style'
|
||||
> & {
|
||||
children?:
|
||||
| React.ReactNode
|
||||
| ((state: PressableStateCallbackType) => React.ReactNode)
|
||||
style?:
|
||||
| StyleProp<ViewStyle>
|
||||
| ((state: PressableStateCallbackType) => StyleProp<ViewStyle>)
|
||||
}
|
||||
|
||||
/**
|
||||
* React Native Pressable with react-native-web's callback state represented in
|
||||
* its types. The web-only fields are optional because native does not provide
|
||||
* them at runtime.
|
||||
*/
|
||||
export function Pressable({children, style, ...props}: PressableProps) {
|
||||
return (
|
||||
<NativePressable {...props} style={style}>
|
||||
{children}
|
||||
</NativePressable>
|
||||
)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react'
|
||||
import {TextInput, View, type ViewToken} from 'react-native'
|
||||
import {type ListViewToken as ViewToken, TextInput, View} from 'react-native'
|
||||
import {type ModerationOpts} from '@bsky/sdk/moderation'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
|
||||
@@ -139,7 +139,7 @@ function DialogInner({guide}: {guide?: Follow10ProgressGuide}) {
|
||||
const [searchText, setSearchText] = useState(lastSearchText)
|
||||
const moderationOpts = useModerationOpts()
|
||||
const listRef = useRef<ListMethods>(null)
|
||||
const inputRef = useRef<TextInput>(null)
|
||||
const inputRef = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
const [headerHeight, setHeaderHeight] = useState(0)
|
||||
const {currentAccount} = useSession()
|
||||
|
||||
@@ -374,7 +374,7 @@ let Header = ({
|
||||
interestsDisplayNames,
|
||||
}: {
|
||||
guide?: Follow10ProgressGuide
|
||||
inputRef: React.RefObject<TextInput | null>
|
||||
inputRef: React.RefObject<React.ComponentRef<typeof TextInput> | null>
|
||||
listRef: React.RefObject<ListMethods | null>
|
||||
onSelectTab: (v: string) => void
|
||||
searchText: string
|
||||
@@ -679,7 +679,7 @@ function SearchInput({
|
||||
}: {
|
||||
onChangeText: (text: string) => void
|
||||
onEscape: () => void
|
||||
inputRef: React.RefObject<TextInput | null>
|
||||
inputRef: React.RefObject<React.ComponentRef<typeof TextInput> | null>
|
||||
defaultValue: string
|
||||
}) {
|
||||
const t = useTheme()
|
||||
|
||||
@@ -93,7 +93,7 @@ export function RichText({
|
||||
|
||||
if (!facets?.length) {
|
||||
if (isOnlyEmoji(text)) {
|
||||
const flattenedStyle = flatten(style) ?? {}
|
||||
const flattenedStyle = flatten(style)
|
||||
const fontSize =
|
||||
(flattenedStyle.fontSize ?? a.text_sm.fontSize) * emojiMultiplier
|
||||
return (
|
||||
|
||||
@@ -100,7 +100,16 @@ export function Trigger({children, hitSlop, label}: TriggerProps) {
|
||||
} else {
|
||||
return (
|
||||
<Button
|
||||
hitSlop={hitSlop}
|
||||
hitSlop={
|
||||
typeof hitSlop === 'number'
|
||||
? {
|
||||
top: hitSlop,
|
||||
right: hitSlop,
|
||||
bottom: hitSlop,
|
||||
left: hitSlop,
|
||||
}
|
||||
: (hitSlop ?? undefined)
|
||||
}
|
||||
label={label}
|
||||
onPress={control.open}
|
||||
style={[a.flex_1, a.justify_between, a.pl_lg, a.pr_md]}
|
||||
|
||||
@@ -3,7 +3,7 @@ import {View} from 'react-native'
|
||||
import {Select as RadixSelect} from 'radix-ui'
|
||||
|
||||
import {useA11y} from '#/state/a11y'
|
||||
import {atoms as a, flatten, useTheme, web} from '#/alf'
|
||||
import {atoms as a, flatten, flattenToCSS, useTheme, web} from '#/alf'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
|
||||
import {
|
||||
@@ -94,7 +94,7 @@ export function Trigger({children, label}: TriggerProps) {
|
||||
onBlur={onBlur}
|
||||
onMouseEnter={onMouseEnter}
|
||||
onMouseLeave={onMouseLeave}
|
||||
style={flatten([
|
||||
style={flattenToCSS([
|
||||
a.flex,
|
||||
a.relative,
|
||||
t.atoms.bg_contrast_50,
|
||||
@@ -196,7 +196,7 @@ export function Content<T>({
|
||||
return (
|
||||
<RadixSelect.Portal>
|
||||
<RadixSelect.Content
|
||||
style={flatten([t.atoms.bg, a.rounded_sm, a.overflow_hidden])}
|
||||
style={flattenToCSS([t.atoms.bg, a.rounded_sm, a.overflow_hidden])}
|
||||
position="popper"
|
||||
align="center"
|
||||
sideOffset={5}
|
||||
@@ -212,17 +212,17 @@ export function Content<T>({
|
||||
a.overflow_hidden,
|
||||
!reduceMotionEnabled && a.zoom_fade_in,
|
||||
]}>
|
||||
<RadixSelect.ScrollUpButton style={flatten(up)}>
|
||||
<RadixSelect.ScrollUpButton style={flattenToCSS(up)}>
|
||||
<ChevronUpIcon style={[t.atoms.text]} size="xs" />
|
||||
</RadixSelect.ScrollUpButton>
|
||||
<RadixSelect.Viewport style={flatten([a.p_xs])}>
|
||||
<RadixSelect.Viewport style={flattenToCSS([a.p_xs])}>
|
||||
{items.map((item, index) => (
|
||||
<Fragment key={valueExtractor(item)}>
|
||||
{renderItem(item, index, selectedValue)}
|
||||
</Fragment>
|
||||
))}
|
||||
</RadixSelect.Viewport>
|
||||
<RadixSelect.ScrollDownButton style={flatten(down)}>
|
||||
<RadixSelect.ScrollDownButton style={flattenToCSS(down)}>
|
||||
<ChevronDownIcon style={[t.atoms.text]} size="xs" />
|
||||
</RadixSelect.ScrollDownButton>
|
||||
</View>
|
||||
@@ -273,7 +273,7 @@ export function Item({ref, value, style, children}: ItemProps) {
|
||||
onMouseLeave={onMouseLeave}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
style={flatten([
|
||||
style={flattenToCSS([
|
||||
t.atoms.text,
|
||||
a.relative,
|
||||
a.flex,
|
||||
@@ -307,7 +307,7 @@ export const ItemText = function ItemText({children, style}: ItemTextProps) {
|
||||
export function ItemIndicator({icon: Icon = CheckIcon}: ItemIndicatorProps) {
|
||||
return (
|
||||
<RadixSelect.ItemIndicator
|
||||
style={flatten([
|
||||
style={flattenToCSS([
|
||||
a.absolute,
|
||||
{left: 0, width: 30},
|
||||
a.flex,
|
||||
@@ -324,7 +324,7 @@ export function Separator() {
|
||||
|
||||
return (
|
||||
<RadixSelect.Separator
|
||||
style={flatten([
|
||||
style={flattenToCSS([
|
||||
{
|
||||
height: 1,
|
||||
backgroundColor: t.atoms.border_contrast_low.borderColor,
|
||||
|
||||
@@ -2,10 +2,6 @@ import {forwardRef, useCallback, useImperativeHandle, useState} from 'react'
|
||||
import {type ListRenderItemInfo, View} from 'react-native'
|
||||
import {AtUri} from '@atproto/syntax'
|
||||
import {type ModerationOpts} from '@bsky/sdk/moderation'
|
||||
import {
|
||||
type InfiniteData,
|
||||
type UseInfiniteQueryResult,
|
||||
} from '@tanstack/react-query'
|
||||
|
||||
import {useBottomBarOffset} from '#/lib/hooks/useBottomBarOffset'
|
||||
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
|
||||
@@ -26,9 +22,6 @@ function keyExtractor(item: app.bsky.actor.defs.ProfileView, index: number) {
|
||||
|
||||
interface ProfilesListProps {
|
||||
listUri: string
|
||||
listMembersQuery: UseInfiniteQueryResult<
|
||||
InfiniteData<app.bsky.graph.getList.$OutputBody>
|
||||
>
|
||||
moderationOpts: ModerationOpts
|
||||
headerHeight: number
|
||||
scrollElRef: ListRef
|
||||
|
||||
@@ -27,8 +27,9 @@ import {
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
const TooltipPortal = createPortalGroup()
|
||||
const TooltipProviderContext =
|
||||
createContext<React.RefObject<View | null> | null>(null)
|
||||
const TooltipProviderContext = createContext<React.RefObject<React.ComponentRef<
|
||||
typeof View
|
||||
> | null> | null>(null)
|
||||
|
||||
/**
|
||||
* Provider for Tooltip component. Only needed when you need to position the tooltip relative to a container,
|
||||
@@ -37,7 +38,7 @@ const TooltipProviderContext =
|
||||
* Only really necessary on iOS but can work on Android.
|
||||
*/
|
||||
export function SheetCompatProvider({children}: {children: React.ReactNode}) {
|
||||
const ref = useRef<View | null>(null)
|
||||
const ref = useRef<React.ComponentRef<typeof View> | null>(null)
|
||||
return (
|
||||
<GlobalGestureEventsProvider style={[a.flex_1]}>
|
||||
<TooltipPortal.Provider>
|
||||
@@ -154,7 +155,7 @@ export function Outer({
|
||||
export function Target({children}: {children: React.ReactNode}) {
|
||||
const {shouldMeasure, setTargetMeasurements} = useContext(TargetContext)
|
||||
const [hasLaidOut, setHasLaidOut] = useState(false)
|
||||
const targetRef = useRef<View>(null)
|
||||
const targetRef = useRef<React.ComponentRef<typeof View>>(null)
|
||||
const containerRef = useContext(TooltipProviderContext)
|
||||
const keyboardIsOpen = useIsKeyboardVisible()
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import {View} from 'react-native'
|
||||
import {utils} from '@bsky.app/alf'
|
||||
import {Popover} from 'radix-ui'
|
||||
|
||||
import {atoms as a, flatten, useTheme} from '#/alf'
|
||||
import {atoms as a, flattenToCSS, useTheme} from '#/alf'
|
||||
import {
|
||||
ARROW_SIZE,
|
||||
BUBBLE_MAX_WIDTH,
|
||||
@@ -85,7 +85,7 @@ export function Content({
|
||||
evt.preventDefault()
|
||||
}
|
||||
}}
|
||||
style={flatten([
|
||||
style={flattenToCSS([
|
||||
a.rounded_sm,
|
||||
{
|
||||
backgroundColor: style.surface,
|
||||
|
||||
@@ -25,13 +25,13 @@ export function OTPInput({
|
||||
label: string
|
||||
value: string
|
||||
onChange: (text: string) => void
|
||||
ref?: React.Ref<TextInput>
|
||||
ref?: React.Ref<React.ComponentRef<typeof TextInput>>
|
||||
numberOfDigits?: number
|
||||
onComplete?: (code: string) => void
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const innerRef = useRef<TextInput>(null)
|
||||
const innerRef = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
|
||||
const [selection, setSelection] = useState({start: 0, end: 0})
|
||||
|
||||
|
||||
@@ -383,7 +383,7 @@ export function ViewMatches({
|
||||
ref={listRef}
|
||||
data={items}
|
||||
renderItem={renderItem}
|
||||
ListFooterComponent={!isEmpty ? <ListFooter height={20} /> : null}
|
||||
ListFooterComponent={!isEmpty ? <ListFooter height={20} /> : undefined}
|
||||
keyExtractor={keyExtractor}
|
||||
keyboardDismissMode="interactive"
|
||||
automaticallyAdjustKeyboardInsets
|
||||
|
||||
@@ -97,7 +97,7 @@ export function SearchablePeopleList({
|
||||
const [headerHeight, setHeaderHeight] = useState(0)
|
||||
const listRef = useRef<ListMethods>(null)
|
||||
const {currentAccount} = useSession()
|
||||
const inputRef = useRef<TextInput>(null)
|
||||
const inputRef = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
|
||||
const [searchText, setSearchText] = useState('')
|
||||
|
||||
@@ -634,7 +634,7 @@ function SearchInput({
|
||||
value: string
|
||||
onChangeText: (text: string) => void
|
||||
onEscape: () => void
|
||||
inputRef: React.RefObject<TextInput | null>
|
||||
inputRef: React.RefObject<React.ComponentRef<typeof TextInput> | null>
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
|
||||
@@ -182,9 +182,9 @@ function ListsContent({
|
||||
<View style={[a.align_center, a.py_lg]}>
|
||||
<Loader size="lg" />
|
||||
</View>
|
||||
) : null
|
||||
) : undefined
|
||||
}
|
||||
ListEmptyComponent={!isLoading && data ? <Empty /> : null}
|
||||
ListEmptyComponent={!isLoading && data ? <Empty /> : undefined}
|
||||
webInnerContentContainerStyle={[a.py_0]}
|
||||
style={platform({
|
||||
web: [a.px_2xl, a.pb_md],
|
||||
|
||||
@@ -134,7 +134,7 @@ export function AddMembersFlow({
|
||||
const [footerHeight, setFooterHeight] = useState(0)
|
||||
|
||||
const listRef = useRef<ListMethods>(null)
|
||||
const inputRef = useRef<TextInput>(null)
|
||||
const inputRef = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
|
||||
const [{groupChatDids, groupChatProfiles, searchText}, dispatch] = useReducer(
|
||||
reducer,
|
||||
@@ -502,7 +502,7 @@ export function AddMembersFlow({
|
||||
<View style={[a.flex_1, a.align_center, a.justify_center]}>
|
||||
<Loader size="xl" />
|
||||
</View>
|
||||
) : null
|
||||
) : undefined
|
||||
}
|
||||
keyExtractor={(item: Item) => item.key}
|
||||
style={[
|
||||
|
||||
@@ -249,7 +249,7 @@ export function InitiateChatFlow({
|
||||
const listRef = useRef<ListMethods>(null)
|
||||
const {currentAccount} = useSession()
|
||||
const aa = useAgeAssurance()
|
||||
const inputRef = useRef<TextInput>(null)
|
||||
const inputRef = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
const accountTooNewPromptControl = Dialog.useDialogControl()
|
||||
|
||||
const {data: convos} = useListConvosQuery({
|
||||
|
||||
@@ -255,7 +255,7 @@ function ReactionTabs({
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
|
||||
const scrollViewRef = useRef<ScrollView>(null)
|
||||
const scrollViewRef = useRef<React.ComponentRef<typeof ScrollView>>(null)
|
||||
const scrollState = useRef({x: 0, width: 0})
|
||||
const tabLayouts = useRef<Map<string, {x: number; width: number}>>(new Map())
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ export function UserSearchInput({
|
||||
value: string
|
||||
onChangeText: (text: string) => void
|
||||
onEscape: () => void
|
||||
inputRef: React.RefObject<TextInput | null>
|
||||
inputRef: React.RefObject<React.ComponentRef<typeof TextInput> | null>
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
|
||||
@@ -11,7 +11,7 @@ import {normalizeTextStyles} from '#/alf/typography'
|
||||
import {IS_ANDROID, IS_IOS, IS_WEB} from '#/env'
|
||||
|
||||
export type AutosizedTextareaProps = Omit<TextInputProps, 'multiline'> & {
|
||||
ref?: React.Ref<TextInput>
|
||||
ref?: React.Ref<React.ComponentRef<typeof TextInput>>
|
||||
label: string
|
||||
minRows?: number
|
||||
maxRows?: number
|
||||
@@ -43,7 +43,7 @@ export function AutosizedTextarea({
|
||||
...rest
|
||||
}: AutosizedTextareaProps) {
|
||||
const {theme: t, fonts} = useAlf()
|
||||
const internalRef = useRef<TextInput>(null)
|
||||
const internalRef = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
const {style, minInputHeight, maxInputHeight, verticalContentPadding} =
|
||||
useMemo(() => {
|
||||
const normalizedStyles = normalizeTextStyles(
|
||||
@@ -180,7 +180,7 @@ export function AutosizedTextarea({
|
||||
]}
|
||||
{...rest}
|
||||
ref={mergeRefs([
|
||||
(node: TextInput | null) => {
|
||||
(node: React.ComponentRef<typeof TextInput> | null) => {
|
||||
internalRef.current = node
|
||||
// bop resize on first render
|
||||
if (IS_WEB && node) handleResizeWeb()
|
||||
|
||||
@@ -62,7 +62,7 @@ export function DateField({
|
||||
<TextField.Icon icon={CalendarDays} />
|
||||
<Input
|
||||
value={value === '' ? '' : toSimpleDateString(value)}
|
||||
inputRef={inputRef as React.Ref<TextInput>}
|
||||
inputRef={inputRef as React.Ref<React.ComponentRef<typeof TextInput>>}
|
||||
label={label}
|
||||
onChange={handleOnChange}
|
||||
testID={testID}
|
||||
|
||||
@@ -19,7 +19,7 @@ type Props = Omit<TextField.InputProps, 'label'> & {
|
||||
*/
|
||||
onClearText?: () => void
|
||||
hotkey?: boolean
|
||||
ref?: React.Ref<TextInput>
|
||||
ref?: React.Ref<React.ComponentRef<typeof TextInput>>
|
||||
}
|
||||
|
||||
export function SearchInput({
|
||||
@@ -33,7 +33,7 @@ export function SearchInput({
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
const showClear = value && value.length > 0
|
||||
const internalRef = useRef<TextInput>(null)
|
||||
const internalRef = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
|
||||
useEffect(() => {
|
||||
if (!hotkey) return
|
||||
|
||||
@@ -14,6 +14,8 @@ import {mergeRefs} from '#/lib/merge-refs'
|
||||
import {
|
||||
applyFonts,
|
||||
atoms as a,
|
||||
flatten,
|
||||
type MutableTextStyle,
|
||||
platform,
|
||||
type TextStyleProp,
|
||||
tokens,
|
||||
@@ -26,7 +28,7 @@ import {type Props as SVGIconProps} from '#/components/icons/common'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
const Context = createContext<{
|
||||
inputRef: React.RefObject<TextInput | null> | null
|
||||
inputRef: React.RefObject<React.ComponentRef<typeof TextInput> | null> | null
|
||||
isInvalid: boolean
|
||||
hovered: boolean
|
||||
onHoverIn: () => void
|
||||
@@ -55,7 +57,7 @@ export type RootProps = React.PropsWithChildren<
|
||||
>
|
||||
|
||||
export function Root({children, isInvalid = false, style}: RootProps) {
|
||||
const inputRef = useRef<TextInput>(null)
|
||||
const inputRef = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
const {
|
||||
state: hovered,
|
||||
onIn: onHoverIn,
|
||||
@@ -161,7 +163,9 @@ export type InputProps = Omit<
|
||||
value?: string
|
||||
onChangeText?: (value: string) => void
|
||||
isInvalid?: boolean
|
||||
inputRef?: React.RefObject<TextInput | null> | React.ForwardedRef<TextInput>
|
||||
inputRef?:
|
||||
| React.RefObject<React.ComponentRef<typeof TextInput> | null>
|
||||
| React.ForwardedRef<React.ComponentRef<typeof TextInput>>
|
||||
/**
|
||||
* Note: this currently falls back to the label if not specified. However,
|
||||
* most new designs have no placeholder. We should eventually remove this fallback
|
||||
@@ -208,34 +212,36 @@ export function createInput(Component: typeof TextInput) {
|
||||
|
||||
const refs = mergeRefs([ctx.inputRef, inputRef!].filter(Boolean))
|
||||
|
||||
const flattened = StyleSheet.flatten<TextStyle>([
|
||||
a.relative,
|
||||
a.z_20,
|
||||
a.flex_1,
|
||||
a.text_md,
|
||||
t.atoms.text,
|
||||
a.px_xs,
|
||||
{
|
||||
// paddingVertical doesn't work w/multiline - esb
|
||||
lineHeight: a.text_md.fontSize * 1.2,
|
||||
textAlignVertical: rest.multiline ? 'top' : undefined,
|
||||
minHeight: rest.multiline ? 80 : undefined,
|
||||
minWidth: 0,
|
||||
paddingTop: 13,
|
||||
paddingBottom: 13,
|
||||
},
|
||||
/*
|
||||
* Margins are needed here to avoid autofill background overlapping the
|
||||
* top and bottom borders - esb
|
||||
*/
|
||||
web({
|
||||
paddingTop: 11,
|
||||
paddingBottom: 11,
|
||||
marginTop: 2,
|
||||
marginBottom: 2,
|
||||
}),
|
||||
style,
|
||||
])
|
||||
const flattened: MutableTextStyle = {
|
||||
...flatten([
|
||||
a.relative,
|
||||
a.z_20,
|
||||
a.flex_1,
|
||||
a.text_md,
|
||||
t.atoms.text,
|
||||
a.px_xs,
|
||||
{
|
||||
// paddingVertical doesn't work w/multiline - esb
|
||||
lineHeight: a.text_md.fontSize * 1.2,
|
||||
textAlignVertical: rest.multiline ? 'top' : undefined,
|
||||
minHeight: rest.multiline ? 80 : undefined,
|
||||
minWidth: 0,
|
||||
paddingTop: 13,
|
||||
paddingBottom: 13,
|
||||
},
|
||||
/*
|
||||
* Margins are needed here to avoid autofill background overlapping the
|
||||
* top and bottom borders - esb
|
||||
*/
|
||||
web({
|
||||
paddingTop: 11,
|
||||
paddingBottom: 11,
|
||||
marginTop: 2,
|
||||
marginBottom: 2,
|
||||
}),
|
||||
style,
|
||||
]),
|
||||
}
|
||||
|
||||
applyFonts(flattened, fonts.family)
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import {useEffect} from 'react'
|
||||
import {
|
||||
Keyboard,
|
||||
type KeyboardEventListener,
|
||||
type KeyboardEvent,
|
||||
type KeyboardEventName,
|
||||
} from 'react-native'
|
||||
|
||||
export function useOnKeyboard(
|
||||
eventName: KeyboardEventName,
|
||||
cb: KeyboardEventListener,
|
||||
cb: (event: KeyboardEvent) => unknown,
|
||||
) {
|
||||
useEffect(() => {
|
||||
const subscription = Keyboard.addListener(eventName, cb)
|
||||
|
||||
@@ -65,7 +65,7 @@ interface GalleryProps {
|
||||
}
|
||||
|
||||
const Context = createContext<{
|
||||
bleedRef: React.RefObject<View | null>
|
||||
bleedRef: React.RefObject<React.ComponentRef<typeof View> | null>
|
||||
bleedWidth: number
|
||||
}>({
|
||||
bleedRef: {current: null},
|
||||
@@ -73,7 +73,7 @@ const Context = createContext<{
|
||||
})
|
||||
|
||||
export function GalleryBleed({children}: {children: React.ReactNode}) {
|
||||
const ref = useRef<View>(null)
|
||||
const ref = useRef<React.ComponentRef<typeof View>>(null)
|
||||
const [bleedWidth, setBleedWidth] = useState(0)
|
||||
|
||||
if (!isValidElement(children)) {
|
||||
@@ -145,7 +145,7 @@ export function Gallery({
|
||||
* scroll position, so it works correctly for off-screen FlatList items.
|
||||
*/
|
||||
const {bleedRef, bleedWidth} = useGalleryBleed()
|
||||
const contentRef = useRef<View>(null)
|
||||
const contentRef = useRef<React.ComponentRef<typeof View>>(null)
|
||||
const [contentDims, setContentDims] = useState<{x: number; width: number}>()
|
||||
const measure = () => {
|
||||
if (contentRef.current && bleedRef.current) {
|
||||
@@ -168,7 +168,9 @@ export function Gallery({
|
||||
|
||||
const flatListRef = useRef<FlatList>(null)
|
||||
const itemWidthsRef = useRef<Map<number, number>>(new Map())
|
||||
const itemRefsRef = useRef<Map<number, View>>(new Map())
|
||||
const itemRefsRef = useRef<Map<number, React.ComponentRef<typeof View>>>(
|
||||
new Map(),
|
||||
)
|
||||
const containerRefsRef = useRef<Map<number, AnimatedRef<any>>>(new Map())
|
||||
const thumbDimsRef = useRef<Map<number, Dimensions>>(new Map())
|
||||
const currentIndexRef = useRef(0)
|
||||
@@ -408,7 +410,7 @@ function GalleryImage({
|
||||
index: number
|
||||
imageCount: number
|
||||
onWidthChange: (index: number, width: number) => void
|
||||
itemRef: (node: View | null) => void
|
||||
itemRef: (node: React.ComponentRef<typeof View> | null) => void
|
||||
largeAltBadge?: boolean
|
||||
onContainerRef: (index: number, ref: AnimatedRef<any>) => void
|
||||
onThumbDims: (index: number, dims: Dimensions) => void
|
||||
|
||||
@@ -226,7 +226,7 @@ function BlockDialogInner({
|
||||
<View style={[a.py_lg, a.align_center, a.justify_center]}>
|
||||
<Loader size="lg" />
|
||||
</View>
|
||||
) : null
|
||||
) : undefined
|
||||
}
|
||||
footer={
|
||||
<Dialog.FlatListFooter
|
||||
|
||||
@@ -159,7 +159,7 @@ function Inner(
|
||||
const logger = ax.logger.useChild(ax.logger.Context.ReportDialog)
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
const ref = useRef<ScrollView>(null)
|
||||
const ref = useRef<React.ComponentRef<typeof ScrollView>>(null)
|
||||
const {
|
||||
data: allLabelers,
|
||||
isLoading: isLabelerLoading,
|
||||
|
||||
@@ -60,7 +60,7 @@ function GifPickerBody({
|
||||
control: Dialog.DialogControlProps
|
||||
onSelectGif: (gif: Gif) => void
|
||||
}) {
|
||||
const textInputRef = useRef<TextInput>(null)
|
||||
const textInputRef = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
const listRef = useRef<ListMethods>(null)
|
||||
const [rawSearch, setRawSearch] = useState('')
|
||||
const [activeCategory, setActiveCategory] = useState<string>('trending')
|
||||
|
||||
@@ -89,7 +89,7 @@ export const GifPickerGrid = forwardRef<ListMethods, Props>(
|
||||
onRetry={fetchNextPage}
|
||||
style={{borderTopWidth: 0}}
|
||||
/>
|
||||
) : null
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -15,7 +15,7 @@ export function GifPickerHeader({
|
||||
onEscape,
|
||||
canClear,
|
||||
}: {
|
||||
inputRef: Ref<TextInput>
|
||||
inputRef: Ref<React.ComponentRef<typeof TextInput>>
|
||||
onChangeText: (text: string) => void
|
||||
onClear: () => void
|
||||
onEscape: () => void
|
||||
|
||||
@@ -1 +1,5 @@
|
||||
export {unstable_batchedUpdates as batchedUpdates} from 'react-native'
|
||||
import {unstable_batchedUpdates} from 'react-native'
|
||||
|
||||
export function batchedUpdates(fn: () => void): void {
|
||||
unstable_batchedUpdates(fn, undefined)
|
||||
}
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
import {type ForwardedRef, useEffect, useMemo, useRef} from 'react'
|
||||
import {type ScrollView} from 'react-native'
|
||||
import {Platform} from 'react-native'
|
||||
import {Platform, type ScrollView} from 'react-native'
|
||||
|
||||
import {mergeRefs} from '#/lib/merge-refs'
|
||||
|
||||
type Props<Scrollable extends ScrollView = ScrollView> = {
|
||||
type ScrollViewInstance = React.ComponentRef<typeof ScrollView>
|
||||
|
||||
type Props<Scrollable extends ScrollViewInstance = ScrollViewInstance> = {
|
||||
cursor?: string
|
||||
outerRef?: ForwardedRef<Scrollable>
|
||||
}
|
||||
|
||||
export function useDraggableScroll<Scrollable extends ScrollView = ScrollView>({
|
||||
outerRef,
|
||||
cursor = 'grab',
|
||||
}: Props<Scrollable> = {}) {
|
||||
export function useDraggableScroll<
|
||||
Scrollable extends ScrollViewInstance = ScrollViewInstance,
|
||||
>({outerRef, cursor = 'grab'}: Props<Scrollable> = {}) {
|
||||
const ref = useRef<Scrollable>(null)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -221,7 +221,7 @@ describe('useApplyPullRequestOTAUpdate', () => {
|
||||
)
|
||||
|
||||
const buttons = jest.mocked(Alert.alert).mock.calls[0][2]
|
||||
act(() => buttons?.[1].onPress?.())
|
||||
void act(() => buttons?.[1].onPress?.())
|
||||
|
||||
await waitFor(() => expect(reloadAsync).toHaveBeenCalled())
|
||||
expect(device.set).toHaveBeenCalledWith(['pendingOTAUpdate'], {
|
||||
@@ -256,7 +256,7 @@ describe('useApplyPullRequestOTAUpdate', () => {
|
||||
await act(() => result.current.tryApplyUpdate('pull-request-123'))
|
||||
const buttons = jest.mocked(Alert.alert).mock.calls[0][2]
|
||||
|
||||
act(() => buttons?.[1].onPress?.())
|
||||
void act(() => buttons?.[1].onPress?.())
|
||||
|
||||
await waitFor(() =>
|
||||
expect(Alert.alert).toHaveBeenLastCalledWith(
|
||||
@@ -283,7 +283,7 @@ describe('useApplyPullRequestOTAUpdate', () => {
|
||||
await act(() => result.current.tryApplyUpdate('pull-request-123'))
|
||||
const buttons = jest.mocked(Alert.alert).mock.calls[0][2]
|
||||
|
||||
act(() => buttons?.[1].onPress?.())
|
||||
void act(() => buttons?.[1].onPress?.())
|
||||
|
||||
await waitFor(() =>
|
||||
expect(Alert.alert).toHaveBeenLastCalledWith(
|
||||
|
||||
@@ -452,7 +452,7 @@ export const splash = (scheme: 'light' | 'dark') => {
|
||||
: require('../../../assets/splash/splash-dark.png')
|
||||
|
||||
return {
|
||||
image: RNImage.resolveAssetSource(source).uri,
|
||||
image: RNImage.resolveAssetSource(source)!.uri,
|
||||
imageFullScreen: true,
|
||||
imageResizeMode: 'cover',
|
||||
backgroundColor: scheme === 'light' ? '#006AFF' : '#002861',
|
||||
|
||||
@@ -23,6 +23,7 @@ export async function openPicker(opts?: ImagePickerOptions) {
|
||||
quality: 1,
|
||||
selectionLimit: 1,
|
||||
...opts,
|
||||
shouldDownloadFromNetwork: true,
|
||||
legacy: true,
|
||||
preferredAssetRepresentationMode:
|
||||
UIImagePickerPreferredAssetRepresentationMode.Automatic,
|
||||
@@ -63,6 +64,7 @@ export async function openUnifiedPicker({
|
||||
// the `file` returned on each asset after validation instead.
|
||||
base64: false,
|
||||
selectionLimit: IS_IOS ? selectionCountRemaining : undefined,
|
||||
shouldDownloadFromNetwork: true,
|
||||
preferredAssetRepresentationMode:
|
||||
UIImagePickerPreferredAssetRepresentationMode.Automatic,
|
||||
videoExportPreset: VideoExportPreset.Passthrough,
|
||||
|
||||
@@ -78,8 +78,8 @@ export const LoginForm = ({
|
||||
const [identifier, setIdentifier] = useState(initialHandle || '')
|
||||
const [identifierFocused, setIdentifierFocused] = useState(false)
|
||||
const [authFactorToken, setAuthFactorToken] = useState('')
|
||||
const identifierRef = useRef<TextInput>(null)
|
||||
const passwordRef = useRef<TextInput>(null)
|
||||
const identifierRef = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
const passwordRef = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
const hasFocusedOnce = useRef(false)
|
||||
const [hasPassword, setHasPassword] = useState(false)
|
||||
const [revealPassword, setRevealPassword] = useState(false)
|
||||
|
||||
@@ -379,7 +379,7 @@ function JoinRequestsList({
|
||||
style={[a.flex_1, a.align_center, a.justify_center, a.py_4xl]}>
|
||||
<Loader size="xl" />
|
||||
</View>
|
||||
) : null
|
||||
) : undefined
|
||||
}
|
||||
contentContainerStyle={
|
||||
showFooter ? {paddingBottom: footerHeight} : undefined
|
||||
|
||||
@@ -189,7 +189,7 @@ export function MessageComposer({
|
||||
<View
|
||||
collapsable={false}
|
||||
ref={native(
|
||||
(node: View) =>
|
||||
(node: React.ComponentRef<typeof View>) =>
|
||||
void composerInternalApiRef.current?.setAutocompleteAnchor(node),
|
||||
)}>
|
||||
<GlassContainer
|
||||
@@ -209,7 +209,11 @@ export function MessageComposer({
|
||||
composerInternalApiRef.current?.insert(emoji.native)
|
||||
}
|
||||
nextFocusRef={() =>
|
||||
composerInternalApiRef.current?.input?.element
|
||||
composerInternalApiRef.current?.input
|
||||
?.element as unknown as
|
||||
| {focus: () => void}
|
||||
| null
|
||||
| undefined
|
||||
}>
|
||||
<EmojiPicker.Trigger label={l`Open emoji picker`}>
|
||||
{({props, state, control}) => (
|
||||
|
||||
@@ -35,7 +35,7 @@ export function Layout({children}: React.PropsWithChildren<{}>) {
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const onboardDispatch = useOnboardingDispatch()
|
||||
const {state, dispatch} = useOnboardingInternalState()
|
||||
const scrollview = useRef<ScrollView>(null)
|
||||
const scrollview = useRef<React.ComponentRef<typeof ScrollView>>(null)
|
||||
const prevActiveStep = useRef<string>(state.activeStep)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {useRef, useState} from 'react'
|
||||
import {View} from 'react-native'
|
||||
import PagerView from 'react-native-pager-view'
|
||||
import {type PagerViewOnPageSelectedEvent} from 'react-native-pager-view'
|
||||
import {Image} from 'expo-image'
|
||||
import {msg} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -36,7 +37,7 @@ export function ValuePropositionPager({
|
||||
ref={ref}
|
||||
style={[a.flex_1]}
|
||||
initialPage={step}
|
||||
onPageSelected={evt => {
|
||||
onPageSelected={(evt: PagerViewOnPageSelectedEvent) => {
|
||||
const page = evt.nativeEvent.position as 0 | 1 | 2
|
||||
if (step !== page) {
|
||||
setActivePage(page)
|
||||
|
||||
@@ -378,7 +378,7 @@ function SuggestedProfileCard({
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const ax = useAnalytics()
|
||||
const cardRef = useRef<View>(null)
|
||||
const cardRef = useRef<React.ComponentRef<typeof View>>(null)
|
||||
const hasTrackedRef = useRef(false)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -160,8 +160,8 @@ export function PostThread({uri}: {uri: string}) {
|
||||
const totalParentCount = useRef(0) // recomputed below
|
||||
const totalChildrenCount = useRef(thread.data.items.length) // recomputed below
|
||||
const listRef = useRef<ListMethods>(null)
|
||||
const anchorRef = useRef<View | null>(null)
|
||||
const headerRef = useRef<View | null>(null)
|
||||
const anchorRef = useRef<React.ComponentRef<typeof View> | null>(null)
|
||||
const headerRef = useRef<React.ComponentRef<typeof View> | null>(null)
|
||||
|
||||
/*
|
||||
* On a cold load, parents are not prepended until the anchor post has
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import {useCallback, useMemo, useRef, useState} from 'react'
|
||||
import {View, type ViewabilityConfig} from 'react-native'
|
||||
import {View} from 'react-native'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {type ViewabilityConfig} from '@react-native/virtualized-lists'
|
||||
import {useQueryClient} from '@tanstack/react-query'
|
||||
import * as bcp47Match from 'bcp-47-match'
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ export function SearchScreenShell({
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const navigation = useNavigation<NavigationProp>()
|
||||
const route = useRoute()
|
||||
const textInput = useRef<TextInput>(null)
|
||||
const textInput = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
const {t: l} = useLingui()
|
||||
const {currentAccount} = useSession()
|
||||
const queryClient = useQueryClient()
|
||||
|
||||
@@ -28,7 +28,7 @@ export function ClearableInput({
|
||||
}) {
|
||||
const t = useTheme()
|
||||
const {t: l} = useLingui()
|
||||
const inputRef = useRef<TextInput>(null)
|
||||
const inputRef = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
const [showClear, setShowClear] = useState(defaultValue.length > 0)
|
||||
|
||||
return (
|
||||
|
||||
@@ -137,8 +137,8 @@ function DialogInner({
|
||||
const [dateUntilActive, setDateUntilActive] = useState(!!parsed.until)
|
||||
|
||||
const [filters, setFilters] = useState<AdvancedFilter[]>(parsed.filters)
|
||||
const scrollRef = useRef<ScrollView>(null)
|
||||
const filtersSectionRef = useRef<View>(null)
|
||||
const scrollRef = useRef<React.ComponentRef<typeof ScrollView>>(null)
|
||||
const filtersSectionRef = useRef<React.ComponentRef<typeof View>>(null)
|
||||
|
||||
function addFilter() {
|
||||
if (filters.length >= MAX_FILTERS) return
|
||||
|
||||
@@ -37,7 +37,7 @@ export function SearchAutocompleteInput({
|
||||
* keystroke while the input is still focused.
|
||||
*/
|
||||
const [dismissed, setDismissed] = useState(false)
|
||||
const inputRef = useRef<TextInput>(null)
|
||||
const inputRef = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
|
||||
const sift = useSift({
|
||||
offset: a.p_sm.padding,
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {type ImageSourcePropType} from 'react-native'
|
||||
import {type ImageSource} from 'expo-image'
|
||||
import type * as DynamicAppIcon from '@bsky.app/expo-dynamic-app-icon'
|
||||
|
||||
export type AppIconSet = {
|
||||
id: DynamicAppIcon.IconName
|
||||
name: string
|
||||
iosImage: () => ImageSourcePropType
|
||||
androidImage: () => ImageSourcePropType
|
||||
iosImage: () => ImageSource
|
||||
androidImage: () => ImageSource
|
||||
}
|
||||
|
||||
@@ -128,7 +128,7 @@ export function ActivityNotificationSettingsScreen({}: Props) {
|
||||
onEndReached={onEndReached}
|
||||
onEndReachedThreshold={4}
|
||||
ListEmptyComponent={
|
||||
error ? null : (
|
||||
error ? undefined : (
|
||||
<View style={[a.px_xl, a.py_md]}>
|
||||
{!isPending ? (
|
||||
<Admonition.Outer type="tip">
|
||||
|
||||
@@ -73,7 +73,7 @@ function DeleteAccountDialogInner({
|
||||
control: DialogOuterProps['control']
|
||||
deactivateDialogControl: DialogOuterProps['control']
|
||||
}) {
|
||||
const passwordRef = useRef<TextInput | null>(null)
|
||||
const passwordRef = useRef<React.ComponentRef<typeof TextInput> | null>(null)
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const cleanError = useCleanError()
|
||||
|
||||
@@ -68,8 +68,8 @@ export function StepInfo({
|
||||
const prevEmailValueRef = useRef<string>(state.email)
|
||||
const passwordValueRef = useRef<string>(state.password)
|
||||
|
||||
const emailInputRef = useRef<TextInput>(null)
|
||||
const passwordInputRef = useRef<TextInput>(null)
|
||||
const emailInputRef = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
const passwordInputRef = useRef<React.ComponentRef<typeof TextInput>>(null)
|
||||
const birthdateInputRef = useRef<DateFieldRef>(null)
|
||||
|
||||
const aaRegionConfig = useAgeAssuranceRegionConfigWithFallback()
|
||||
|
||||
@@ -41,6 +41,7 @@ import {
|
||||
} from '#/state/shell/progress-guide'
|
||||
import {PagerWithHeader} from '#/view/com/pager/PagerWithHeader'
|
||||
import {ProfileSubpageHeader} from '#/view/com/profile/ProfileSubpageHeader'
|
||||
import {type ListRef} from '#/view/com/util/List'
|
||||
import {bulkWriteFollows} from '#/screens/Onboarding/util'
|
||||
import {atoms as a, useBreakpoints, useTheme} from '#/alf'
|
||||
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||
@@ -251,8 +252,7 @@ function StarterPackScreenLoaded({
|
||||
// Validated above
|
||||
listUri={starterPack.list!.uri}
|
||||
headerHeight={headerHeight}
|
||||
// @ts-expect-error
|
||||
scrollElRef={scrollElRef}
|
||||
scrollElRef={scrollElRef as ListRef}
|
||||
moderationOpts={moderationOpts}
|
||||
/>
|
||||
)
|
||||
@@ -260,11 +260,9 @@ function StarterPackScreenLoaded({
|
||||
{showFeedsTab
|
||||
? ({headerHeight, scrollElRef}) => (
|
||||
<FeedsList
|
||||
// @ts-expect-error ?
|
||||
feeds={starterPack?.feeds}
|
||||
feeds={starterPack.feeds!}
|
||||
headerHeight={headerHeight}
|
||||
// @ts-expect-error
|
||||
scrollElRef={scrollElRef}
|
||||
scrollElRef={scrollElRef as ListRef}
|
||||
/>
|
||||
)
|
||||
: null}
|
||||
@@ -274,9 +272,7 @@ function StarterPackScreenLoaded({
|
||||
// Validated above
|
||||
listUri={starterPack.list!.uri}
|
||||
headerHeight={headerHeight}
|
||||
// @ts-expect-error
|
||||
scrollElRef={scrollElRef}
|
||||
moderationOpts={moderationOpts}
|
||||
scrollElRef={scrollElRef as ListRef}
|
||||
/>
|
||||
)
|
||||
: null}
|
||||
|
||||
@@ -5,8 +5,6 @@ import {
|
||||
Pressable,
|
||||
ScrollView,
|
||||
View,
|
||||
type ViewabilityConfig,
|
||||
type ViewToken,
|
||||
} from 'react-native'
|
||||
import {
|
||||
Gesture,
|
||||
@@ -29,6 +27,10 @@ import {AtUri} from '@atproto/syntax'
|
||||
import {type ModerationDecision} from '@bsky/sdk/moderation'
|
||||
import {RichText as RichTextAPI} from '@bsky/sdk/richtext'
|
||||
import {Trans, useLingui} from '@lingui/react/macro'
|
||||
import {
|
||||
type ListViewToken as ViewToken,
|
||||
type ViewabilityConfig,
|
||||
} from '@react-native/virtualized-lists'
|
||||
import {
|
||||
type RouteProp,
|
||||
useFocusEffect,
|
||||
@@ -413,7 +415,7 @@ function Feed() {
|
||||
|
||||
const onViewableItemsChanged = useCallback(
|
||||
({viewableItems}: {viewableItems: ViewToken[]; changed: ViewToken[]}) => {
|
||||
if (viewableItems[0] && viewableItems[0].index !== null) {
|
||||
if (viewableItems[0]?.index != null) {
|
||||
const newIndex = viewableItems[0].index
|
||||
setCurrentIndex(newIndex)
|
||||
updateVideoState(newIndex)
|
||||
|
||||
@@ -15,10 +15,10 @@ import {Button, ButtonText} from '#/components/Button'
|
||||
import splashImagePointer from '../../../../assets/illustrations/illustration-mobile.png'
|
||||
// @ts-ignore
|
||||
import darkSplashImagePointer from '../../../../assets/illustrations/illustration-mobile-dark.png'
|
||||
const splashImageUri = RNImage.resolveAssetSource(splashImagePointer).uri
|
||||
const splashImageUri = RNImage.resolveAssetSource(splashImagePointer)!.uri
|
||||
const darkSplashImageUri = RNImage.resolveAssetSource(
|
||||
darkSplashImagePointer,
|
||||
).uri
|
||||
)!.uri
|
||||
|
||||
export const SplashScreen = ({
|
||||
onPressSignin,
|
||||
|
||||
@@ -73,7 +73,6 @@ export const SplashScreen = ({
|
||||
style={[
|
||||
a.h_full,
|
||||
a.justify_center,
|
||||
// @ts-expect-error web only
|
||||
{paddingBottom: '20vh'},
|
||||
IS_WEB_MOBILE && a.pb_5xl,
|
||||
t.atoms.border_contrast_medium,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {useCallback, useEffect, useMemo, useState} from 'react'
|
||||
import {Pressable, View} from 'react-native'
|
||||
import {View} from 'react-native'
|
||||
import * as VideoThumbnails from 'expo-video-thumbnails'
|
||||
import {msg, plural} from '@lingui/core/macro'
|
||||
import {useLingui} from '@lingui/react'
|
||||
@@ -15,6 +15,7 @@ import {DotGrid3x1_Stroke2_Corner0_Rounded as DotsIcon} from '#/components/icons
|
||||
import {CloseQuote_Stroke2_Corner0_Rounded as CloseQuoteIcon} from '#/components/icons/Quote'
|
||||
import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
|
||||
import * as MediaPreview from '#/components/MediaPreview'
|
||||
import {Pressable} from '#/components/Pressable'
|
||||
import * as Prompt from '#/components/Prompt'
|
||||
import {RichText} from '#/components/RichText'
|
||||
import {Text} from '#/components/Typography'
|
||||
|
||||
@@ -162,7 +162,7 @@ const GalleryItem = ({
|
||||
const editControl = Dialog.useDialogControl()
|
||||
const [altBtnViewTag, setAltBtnViewTag] = useState<number>()
|
||||
|
||||
const altBtnRef = (node: View | null) => {
|
||||
const altBtnRef = (node: React.ComponentRef<typeof View> | null) => {
|
||||
// for iOS 26 fluid transition
|
||||
if (IS_IOS && node) {
|
||||
const tag = findNodeHandle(node)
|
||||
|
||||
@@ -50,7 +50,7 @@ export function TextInput({
|
||||
}: TextInputProps) {
|
||||
const {t: l} = useLingui()
|
||||
const {theme: t, fonts} = useAlf()
|
||||
const textInput = useRef<RNTextInput>(null)
|
||||
const textInput = useRef<React.ComponentRef<typeof RNTextInput>>(null)
|
||||
const textInputSelection = useRef<Selection>({start: 0, end: 0})
|
||||
const theme = useTheme()
|
||||
const [autocompletePrefix, setAutocompletePrefix] = useState('')
|
||||
@@ -178,9 +178,10 @@ export function TextInput({
|
||||
*/
|
||||
if (IS_ANDROID) {
|
||||
// @ts-ignore
|
||||
style.fontVariant = style.fontVariant
|
||||
? style.fontVariant.join(' ')
|
||||
: undefined
|
||||
style.fontVariant =
|
||||
typeof style.fontVariant === 'string'
|
||||
? style.fontVariant
|
||||
: style.fontVariant?.join(' ')
|
||||
}
|
||||
return style
|
||||
}, [t, fonts])
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {type QueryClient} from '@tanstack/react-query'
|
||||
|
||||
import {atoms as a, flatten} from '#/alf'
|
||||
import {atoms as a, flattenToCSS} from '#/alf'
|
||||
|
||||
export function clearThumbnailCache(_queryClient: QueryClient) {
|
||||
// no-op on web
|
||||
@@ -10,7 +10,7 @@ export function VideoTranscodeBackdrop({uri}: {uri: string}) {
|
||||
return (
|
||||
<video
|
||||
src={uri}
|
||||
style={flatten([
|
||||
style={flattenToCSS([
|
||||
a.absolute,
|
||||
a.inset_0,
|
||||
a.h_full,
|
||||
|
||||
@@ -145,7 +145,6 @@ export function ComposerPrompt() {
|
||||
return (
|
||||
<Pressable
|
||||
onPress={onPress}
|
||||
android_ripple={null}
|
||||
accessibilityRole="button"
|
||||
accessibilityLabel={_(msg`Compose new post`)}
|
||||
accessibilityHint={_(msg`Opens the post composer`)}
|
||||
|
||||
@@ -9,7 +9,7 @@ export function DraggableScrollView({
|
||||
style,
|
||||
...props
|
||||
}: ComponentPropsWithRef<typeof ScrollView>) {
|
||||
const {refs} = useDraggableScroll<ScrollView>({
|
||||
const {refs} = useDraggableScroll<React.ComponentRef<typeof ScrollView>>({
|
||||
outerRef: ref,
|
||||
cursor: 'grab', // optional, default
|
||||
})
|
||||
|
||||
@@ -31,7 +31,9 @@ import {TabBar} from './TabBar'
|
||||
export interface PagerWithHeaderChildParams {
|
||||
headerHeight: number
|
||||
isFocused: boolean
|
||||
scrollElRef: React.MutableRefObject<ListMethods | ScrollView | null>
|
||||
scrollElRef: React.MutableRefObject<
|
||||
ListMethods | React.ComponentRef<typeof ScrollView> | null
|
||||
>
|
||||
}
|
||||
|
||||
export interface PagerWithHeaderProps {
|
||||
@@ -269,7 +271,7 @@ let PagerTabBar = ({
|
||||
],
|
||||
}
|
||||
})
|
||||
const headerRef = useRef<View>(null)
|
||||
const headerRef = useRef<React.ComponentRef<typeof View>>(null)
|
||||
const fallbackHeaderOnlyHeight = useRef(0)
|
||||
return (
|
||||
<Animated.View
|
||||
@@ -378,7 +380,7 @@ function PagerItem({
|
||||
headerHeight,
|
||||
isFocused,
|
||||
scrollElRef: scrollElRef as React.MutableRefObject<
|
||||
ListMethods | ScrollView | null
|
||||
ListMethods | React.ComponentRef<typeof ScrollView> | null
|
||||
>,
|
||||
})}
|
||||
</ScrollProvider>
|
||||
|
||||
@@ -16,7 +16,9 @@ import {TabBar} from './TabBar'
|
||||
export interface PagerWithHeaderChildParams {
|
||||
headerHeight: number
|
||||
isFocused: boolean
|
||||
scrollElRef: React.MutableRefObject<ListMethods | ScrollView | null>
|
||||
scrollElRef: React.MutableRefObject<
|
||||
ListMethods | React.ComponentRef<typeof ScrollView> | null
|
||||
>
|
||||
}
|
||||
|
||||
export interface PagerWithHeaderProps {
|
||||
@@ -180,7 +182,7 @@ function PagerItem({
|
||||
headerHeight: 0,
|
||||
isFocused,
|
||||
scrollElRef: scrollElRef as React.MutableRefObject<
|
||||
ListMethods | ScrollView | null
|
||||
ListMethods | React.ComponentRef<typeof ScrollView> | null
|
||||
>,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ export function TabBar({
|
||||
onPressSelected,
|
||||
}: TabBarProps) {
|
||||
const t = useTheme()
|
||||
const scrollElRef = useRef<ScrollView>(null)
|
||||
const scrollElRef = useRef<React.ComponentRef<typeof ScrollView>>(null)
|
||||
const itemRefs = useRef<Array<Element>>([])
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const styles = gtMobile ? desktopStyles : mobileStyles
|
||||
@@ -47,7 +47,9 @@ export function TabBar({
|
||||
// On the web, the primary interaction is tapping.
|
||||
// Scrolling under tap feels disorienting so only adjust the scroll offset
|
||||
// when tapping on an item out of view--and we adjust by almost an entire page.
|
||||
const parent = scrollElRef?.current?.getScrollableNode?.()
|
||||
const parent = scrollElRef.current?.getScrollableNode?.() as unknown as
|
||||
| HTMLElement
|
||||
| undefined
|
||||
if (!parent) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import {type AlertButton, type AlertStatic} from 'react-native'
|
||||
import {type Alert as RNAlert, type AlertButton} from 'react-native'
|
||||
|
||||
class WebAlert implements Pick<AlertStatic, 'alert'> {
|
||||
class WebAlert implements Pick<typeof RNAlert, 'alert'> {
|
||||
public alert(title: string, message?: string, buttons?: AlertButton[]): void {
|
||||
if (buttons === undefined || buttons.length === 0) {
|
||||
window.alert([title, message].filter(Boolean).join('\n'))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import {forwardRef, memo, useDeferredValue, useMemo} from 'react'
|
||||
import {RefreshControl, type ViewToken} from 'react-native'
|
||||
import {type ListViewToken as ViewToken, RefreshControl} from 'react-native'
|
||||
import {
|
||||
type FlatListPropsWithLayout,
|
||||
useAnimatedScrollHandler,
|
||||
@@ -173,7 +173,6 @@ let List = forwardRef<ListMethods, ListProps>(
|
||||
scrollsToTop={scrollsToTop}
|
||||
scrollEventThrottle={1}
|
||||
style={style}
|
||||
// @ts-expect-error FlatList_INTERNAL ref type is wrong -sfn
|
||||
ref={ref}
|
||||
/>
|
||||
)
|
||||
|
||||
@@ -407,7 +407,7 @@ function ListImpl<ItemT>(
|
||||
overflowY: isWithinSplitView ? 'auto' : 'scroll',
|
||||
},
|
||||
]}
|
||||
ref={nativeRef as unknown as React.RefObject<View>}>
|
||||
ref={nativeRef as unknown as React.Ref<React.ComponentRef<typeof View>>}>
|
||||
<Visibility
|
||||
onVisibleChange={setIsInsideVisibleTree}
|
||||
style={
|
||||
|
||||
@@ -1,26 +1,19 @@
|
||||
import {forwardRef, type PropsWithChildren} from 'react'
|
||||
import {
|
||||
Pressable,
|
||||
type PressableProps,
|
||||
type StyleProp,
|
||||
type View,
|
||||
type ViewStyle,
|
||||
} from 'react-native'
|
||||
import {Pressable, type StyleProp, type ViewStyle} from 'react-native'
|
||||
|
||||
import {addStyle} from '#/lib/styles'
|
||||
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
||||
|
||||
interface PressableWithHover extends PressableProps {
|
||||
interface PressableWithHoverProps extends React.ComponentPropsWithRef<Pressable> {
|
||||
hoverStyle: StyleProp<ViewStyle>
|
||||
}
|
||||
|
||||
export const PressableWithHover = forwardRef<
|
||||
View,
|
||||
PropsWithChildren<PressableWithHover>
|
||||
>(function PressableWithHoverImpl(
|
||||
{children, style, hoverStyle, ...props},
|
||||
export function PressableWithHover({
|
||||
ref,
|
||||
) {
|
||||
children,
|
||||
style,
|
||||
hoverStyle,
|
||||
...props
|
||||
}: PressableWithHoverProps) {
|
||||
const {
|
||||
state: hovered,
|
||||
onIn: onHoverIn,
|
||||
@@ -41,4 +34,4 @@ export const PressableWithHover = forwardRef<
|
||||
{children}
|
||||
</Pressable>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import {forwardRef, memo} from 'react'
|
||||
import {type FlatListComponent, View, type ViewProps} from 'react-native'
|
||||
import {View, type ViewProps} from 'react-native'
|
||||
import Animated from 'react-native-reanimated'
|
||||
import {type FlatListPropsWithLayout} from 'react-native-reanimated'
|
||||
|
||||
// If you explode these into functions, don't forget to forwardRef!
|
||||
|
||||
@@ -10,22 +9,19 @@ import {type FlatListPropsWithLayout} from 'react-native-reanimated'
|
||||
* The types are a bit wrong on `FlatList_INTERNAL`
|
||||
*/
|
||||
export const FlatList_INTERNAL = memo(Animated.FlatList)
|
||||
export type FlatList_INTERNAL<ItemT = any> = Omit<
|
||||
FlatListComponent<ItemT, FlatListPropsWithLayout<ItemT>>,
|
||||
'CellRendererComponent'
|
||||
>
|
||||
export type FlatList_INTERNAL = React.ComponentRef<typeof Animated.FlatList>
|
||||
|
||||
/**
|
||||
* @deprecated use `Layout` components
|
||||
*/
|
||||
export const ScrollView = Animated.ScrollView
|
||||
export type ScrollView = typeof Animated.ScrollView
|
||||
export type ScrollView = React.ComponentRef<typeof Animated.ScrollView>
|
||||
|
||||
/**
|
||||
* @deprecated use `Layout` components
|
||||
*/
|
||||
export const CenteredView = forwardRef<
|
||||
View,
|
||||
React.ComponentRef<typeof View>,
|
||||
React.PropsWithChildren<
|
||||
ViewProps & {sideBorders?: boolean; topBorder?: boolean}
|
||||
>
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
|
||||
import {forwardRef} from 'react'
|
||||
import {
|
||||
type FlatList,
|
||||
type FlatListProps,
|
||||
type ScrollViewProps,
|
||||
StyleSheet,
|
||||
@@ -45,7 +44,7 @@ export const CenteredView = forwardRef(function CenteredView(
|
||||
}: React.PropsWithChildren<
|
||||
ViewProps & {sideBorders?: boolean; topBorder?: boolean}
|
||||
>,
|
||||
ref: React.Ref<View>,
|
||||
ref: React.Ref<React.ComponentRef<typeof View>>,
|
||||
) {
|
||||
const pal = usePalette('default')
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
@@ -76,7 +75,7 @@ export const FlatList_INTERNAL = forwardRef(function FlatListImpl<ItemT>(
|
||||
}: React.PropsWithChildren<
|
||||
Omit<FlatListProps<ItemT>, 'CellRendererComponent'> & AddedProps
|
||||
>,
|
||||
ref: React.Ref<FlatList<ItemT>>,
|
||||
ref: React.Ref<React.ComponentRef<typeof Animated.FlatList>>,
|
||||
) {
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const {centerColumnOffset} = useLayoutBreakpoints()
|
||||
@@ -105,7 +104,6 @@ export const FlatList_INTERNAL = forwardRef(function FlatListImpl<ItemT>(
|
||||
}
|
||||
if (desktopFixedHeight) {
|
||||
if (typeof desktopFixedHeight === 'number') {
|
||||
// @ts-expect-error Web only -prf
|
||||
style = addStyle(style, {
|
||||
height: `calc(100vh - ${desktopFixedHeight}px)`,
|
||||
})
|
||||
@@ -143,7 +141,7 @@ export const FlatList_INTERNAL = forwardRef(function FlatListImpl<ItemT>(
|
||||
*/
|
||||
export const ScrollView = forwardRef(function ScrollViewImpl(
|
||||
{contentContainerStyle, ...props}: React.PropsWithChildren<ScrollViewProps>,
|
||||
ref: React.Ref<Animated.ScrollView>,
|
||||
ref: React.Ref<React.ComponentRef<typeof Animated.ScrollView>>,
|
||||
) {
|
||||
const {isMobile} = useWebMediaQueries()
|
||||
const {centerColumnOffset} = useLayoutBreakpoints()
|
||||
@@ -170,7 +168,6 @@ export const ScrollView = forwardRef(function ScrollViewImpl(
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
contentContainer: {
|
||||
// @ts-expect-error web only
|
||||
minHeight: '100vh',
|
||||
},
|
||||
container: {
|
||||
@@ -189,7 +186,6 @@ const styles = StyleSheet.create({
|
||||
marginRight: 'auto',
|
||||
},
|
||||
fixedHeight: {
|
||||
// @ts-expect-error web only
|
||||
height: '100vh',
|
||||
},
|
||||
})
|
||||
|
||||
@@ -4,8 +4,6 @@ import {
|
||||
type GestureResponderEvent,
|
||||
type NativeSyntheticEvent,
|
||||
type NativeTouchEvent,
|
||||
Pressable,
|
||||
type PressableStateCallbackType,
|
||||
type StyleProp,
|
||||
StyleSheet,
|
||||
type TextStyle,
|
||||
@@ -15,6 +13,10 @@ import {
|
||||
|
||||
import {choose} from '#/lib/functions'
|
||||
import {useTheme} from '#/lib/ThemeContext'
|
||||
import {
|
||||
Pressable,
|
||||
type PressableStateCallbackType,
|
||||
} from '#/components/Pressable'
|
||||
import {Text} from '../text/Text'
|
||||
|
||||
export type ButtonType =
|
||||
@@ -28,15 +30,6 @@ export type ButtonType =
|
||||
| 'secondary-light'
|
||||
| 'default-light'
|
||||
|
||||
// Augment type for react-native-web (see https://github.com/necolas/react-native-web/issues/1684#issuecomment-766451866)
|
||||
declare module 'react-native' {
|
||||
interface PressableStateCallbackType {
|
||||
// @ts-ignore web only
|
||||
hovered?: boolean
|
||||
focused?: boolean
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated use Button from `#/components/Button.tsx` instead
|
||||
*/
|
||||
|
||||
@@ -8,5 +8,5 @@ import {type ListMethods} from './List'
|
||||
*/
|
||||
export function findListNativeTag(list: ListMethods | null): number | null {
|
||||
if (!list) return null
|
||||
return findNodeHandle(list)
|
||||
return findNodeHandle(list) ?? null
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import {UITextView} from '@bsky.app/react-native-uitextview'
|
||||
import {lh, s} from '#/lib/styles'
|
||||
import {type TypographyVariant, useTheme} from '#/lib/ThemeContext'
|
||||
import {logger} from '#/logger'
|
||||
import {applyFonts, useAlf} from '#/alf'
|
||||
import {applyFonts, type MutableTextStyle, useAlf} from '#/alf'
|
||||
import {
|
||||
childHasEmoji,
|
||||
renderChildrenWithEmoji,
|
||||
@@ -61,12 +61,10 @@ function Text_DEPRECATED({
|
||||
const typography = theme.typography[type]
|
||||
const lineHeightStyle = lineHeight ? lh(theme, type, lineHeight) : undefined
|
||||
|
||||
const flattened = StyleSheet.flatten([
|
||||
s.black,
|
||||
typography,
|
||||
lineHeightStyle,
|
||||
style,
|
||||
])
|
||||
const flattened: MutableTextStyle = {
|
||||
...(StyleSheet.flatten([s.black, typography, lineHeightStyle, style]) ??
|
||||
{}),
|
||||
}
|
||||
|
||||
applyFonts(flattened, fonts.family)
|
||||
|
||||
|
||||
@@ -262,7 +262,7 @@ function NotificationsTab({
|
||||
ListHeaderComponent={
|
||||
filter === 'mentions' ? (
|
||||
<DisabledNotificationsWarning active={isFocusedAndActive} />
|
||||
) : null
|
||||
) : undefined
|
||||
}
|
||||
/>
|
||||
{(isScrolledDown || hasNew) && (
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user