Fix a bunch of type errors and add a type-check to the github workflows (#837)

* Add yarn type-check

* Rename to yarn typecheck

* Fix a collection of type errors

* Add typecheck to automated tests

* add `dist` to exluded folders tsconfig

---------

Co-authored-by: Ansh Nanda <anshnanda10@gmail.com>
zio/stable
Paul Frazee 2023-06-02 15:01:04 -05:00 committed by GitHub
parent 46c9de7c18
commit e8843ded5b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
23 changed files with 168 additions and 82 deletions

View File

@ -18,8 +18,10 @@ jobs:
uses: actions/checkout@v3 uses: actions/checkout@v3
- name: Yarn install - name: Yarn install
run: yarn run: yarn
- name: Typescript & Lint check - name: Lint check
run: yarn lint run: yarn lint
- name: Type check
run: yarn typecheck
testing: testing:
name: Run tests name: Run tests
runs-on: ubuntu-latest runs-on: ubuntu-latest

View File

@ -16,6 +16,7 @@
"test-ci": "jest --ci --forceExit --reporters=default --reporters=jest-junit", "test-ci": "jest --ci --forceExit --reporters=default --reporters=jest-junit",
"test-coverage": "jest --coverage", "test-coverage": "jest --coverage",
"lint": "eslint ./src --ext .js,.jsx,.ts,.tsx", "lint": "eslint ./src --ext .js,.jsx,.ts,.tsx",
"typecheck": "tsc --project ./tsconfig.check.json",
"e2e:mock-server": "ts-node __e2e__/mock-server.ts", "e2e:mock-server": "ts-node __e2e__/mock-server.ts",
"e2e:metro": "RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios", "e2e:metro": "RN_SRC_EXT=e2e.ts,e2e.tsx expo run:ios",
"e2e:build": "detox build -c ios.sim.debug", "e2e:build": "detox build -c ios.sim.debug",

View File

@ -11,7 +11,7 @@ export function doPolyfill() {
interface FetchHandlerResponse { interface FetchHandlerResponse {
status: number status: number
headers: Record<string, string> headers: Record<string, string>
body: ArrayBuffer | undefined body: any
} }
async function fetchHandler( async function fetchHandler(

View File

@ -74,9 +74,12 @@ export class FeedViewPostsSlice {
} }
flattenReplyParent() { flattenReplyParent() {
if (this.items[0].reply?.parent) { if (this.items[0].reply) {
const reply = this.items[0].reply
if (AppBskyFeedDefs.isPostView(reply.parent)) {
this.isFlattenedReply = true this.isFlattenedReply = true
this.items.splice(0, 0, {post: this.items[0].reply?.parent}) this.items.splice(0, 0, {post: reply.parent})
}
} }
} }
} }
@ -130,18 +133,19 @@ export class FeedTuner {
// turn non-threads with reply parents into threads // turn non-threads with reply parents into threads
for (const slice of slices) { for (const slice of slices) {
if (!slice.isThread && !slice.items[0].reason && slice.items[0].reply) {
const reply = slice.items[0].reply
if ( if (
!slice.isThread && AppBskyFeedDefs.isPostView(reply.parent) &&
!slice.items[0].reason && !this.seenUris.has(reply.parent.uri) &&
slice.items[0].reply?.parent && !soonToBeSeenUris.has(reply.parent.uri)
!this.seenUris.has(slice.items[0].reply?.parent.uri) &&
!soonToBeSeenUris.has(slice.items[0].reply?.parent.uri)
) { ) {
const uri = slice.items[0].reply?.parent.uri const uri = reply.parent.uri
slice.flattenReplyParent() slice.flattenReplyParent()
soonToBeSeenUris.add(uri) soonToBeSeenUris.add(uri)
} }
} }
}
for (const slice of slices) { for (const slice of slices) {
for (const item of slice.items) { for (const item of slice.items) {
@ -231,7 +235,12 @@ export class FeedTuner {
} }
function getSelfReplyUri(item: FeedViewPost): string | undefined { function getSelfReplyUri(item: FeedViewPost): string | undefined {
return item.reply?.parent.author.did === item.post.author.did if (item.reply) {
? item.reply?.parent.uri if (AppBskyFeedDefs.isPostView(item.reply.parent)) {
return item.reply.parent.author.did === item.post.author.did
? item.reply.parent.uri
: undefined : undefined
} }
}
return undefined
}

View File

@ -4,7 +4,7 @@ import * as React from 'react'
* Helper hook to run persistent timers on views * Helper hook to run persistent timers on views
*/ */
export function useTimer(time: number, handler: () => void) { export function useTimer(time: number, handler: () => void) {
const timer = React.useRef(undefined) const timer = React.useRef<undefined | NodeJS.Timeout>(undefined)
// function to restart the timer // function to restart the timer
const reset = React.useCallback(() => { const reset = React.useCallback(() => {

View File

@ -9,6 +9,16 @@ interface Membership {
value: AppBskyGraphListitem.Record value: AppBskyGraphListitem.Record
} }
interface ListitemRecord {
uri: string
value: AppBskyGraphListitem.Record
}
interface ListitemListResponse {
cursor?: string
records: ListitemRecord[]
}
export class ListMembershipModel { export class ListMembershipModel {
// data // data
memberships: Membership[] = [] memberships: Membership[] = []
@ -32,9 +42,10 @@ export class ListMembershipModel {
// it needs to be replaced with server side list membership queries // it needs to be replaced with server side list membership queries
// -prf // -prf
let cursor let cursor
let records = [] let records: ListitemRecord[] = []
for (let i = 0; i < 100; i++) { for (let i = 0; i < 100; i++) {
const res = await this.rootStore.agent.app.bsky.graph.listitem.list({ const res: ListitemListResponse =
await this.rootStore.agent.app.bsky.graph.listitem.list({
repo: this.rootStore.me.did, repo: this.rootStore.me.did,
cursor, cursor,
limit: PAGE_SIZE, limit: PAGE_SIZE,
@ -99,7 +110,7 @@ export class ListMembershipModel {
}) })
} }
async updateTo(uris: string) { async updateTo(uris: string[]) {
for (const uri of uris) { for (const uri of uris) {
await this.add(uri) await this.add(uri)
} }

View File

@ -4,6 +4,7 @@ import {
AppBskyGraphGetList as GetList, AppBskyGraphGetList as GetList,
AppBskyGraphDefs as GraphDefs, AppBskyGraphDefs as GraphDefs,
AppBskyGraphList, AppBskyGraphList,
AppBskyGraphListitem,
} from '@atproto/api' } from '@atproto/api'
import {Image as RNImage} from 'react-native-image-crop-picker' import {Image as RNImage} from 'react-native-image-crop-picker'
import {RootStoreModel} from '../root-store' import {RootStoreModel} from '../root-store'
@ -13,6 +14,16 @@ import {bundleAsync} from 'lib/async/bundle'
const PAGE_SIZE = 30 const PAGE_SIZE = 30
interface ListitemRecord {
uri: string
value: AppBskyGraphListitem.Record
}
interface ListitemListResponse {
cursor?: string
records: ListitemRecord[]
}
export class ListModel { export class ListModel {
// state // state
isLoading = false isLoading = false
@ -33,7 +44,7 @@ export class ListModel {
name, name,
description, description,
avatar, avatar,
}: {name: string; description: string; avatar: RNImage | undefined}, }: {name: string; description: string; avatar: RNImage | null | undefined},
) { ) {
const record: AppBskyGraphList.Record = { const record: AppBskyGraphList.Record = {
purpose: 'app.bsky.graph.defs#modlist', purpose: 'app.bsky.graph.defs#modlist',
@ -124,6 +135,9 @@ export class ListModel {
description: string description: string
avatar: RNImage | null | undefined avatar: RNImage | null | undefined
}) { }) {
if (!this.list) {
return
}
if (!this.isOwner) { if (!this.isOwner) {
throw new Error('Cannot edit this list') throw new Error('Cannot edit this list')
} }
@ -157,11 +171,16 @@ export class ListModel {
} }
async delete() { async delete() {
if (!this.list) {
return
}
// fetch all the listitem records that belong to this list // fetch all the listitem records that belong to this list
let cursor let cursor
let records = [] let records: ListitemRecord[] = []
for (let i = 0; i < 100; i++) { for (let i = 0; i < 100; i++) {
const res = await this.rootStore.agent.app.bsky.graph.listitem.list({ const res: ListitemListResponse =
await this.rootStore.agent.app.bsky.graph.listitem.list({
repo: this.rootStore.me.did, repo: this.rootStore.me.did,
cursor, cursor,
limit: PAGE_SIZE, limit: PAGE_SIZE,
@ -193,6 +212,9 @@ export class ListModel {
} }
async subscribe() { async subscribe() {
if (!this.list) {
return
}
await this.rootStore.agent.app.bsky.graph.muteActorList({ await this.rootStore.agent.app.bsky.graph.muteActorList({
list: this.list.uri, list: this.list.uri,
}) })
@ -200,6 +222,9 @@ export class ListModel {
} }
async unsubscribe() { async unsubscribe() {
if (!this.list) {
return
}
await this.rootStore.agent.app.bsky.graph.unmuteActorList({ await this.rootStore.agent.app.bsky.graph.unmuteActorList({
list: this.list.uri, list: this.list.uri,
}) })

View File

@ -1,4 +1,7 @@
import {AppBskyActorDefs} from '@atproto/api' import {
AppBskyActorDefs,
AppBskyGraphGetFollows as GetFollows,
} from '@atproto/api'
import {makeAutoObservable, runInAction} from 'mobx' import {makeAutoObservable, runInAction} from 'mobx'
import sampleSize from 'lodash.samplesize' import sampleSize from 'lodash.samplesize'
import {bundleAsync} from 'lib/async/bundle' import {bundleAsync} from 'lib/async/bundle'
@ -43,7 +46,8 @@ export class FoafsModel {
{ {
let cursor let cursor
for (let i = 0; i < 10; i++) { for (let i = 0; i < 10; i++) {
const res = await this.rootStore.agent.getFollows({ const res: GetFollows.Response =
await this.rootStore.agent.getFollows({
actor: this.rootStore.me.did, actor: this.rootStore.me.did,
cursor, cursor,
limit: 100, limit: 100,

View File

@ -67,7 +67,7 @@ export class PostsFeedItemModel {
} }
get rootUri(): string { get rootUri(): string {
if (this.reply?.root.uri) { if (typeof this.reply?.root.uri === 'string') {
return this.reply.root.uri return this.reply.root.uri
} }
return this.post.uri return this.post.uri

View File

@ -61,7 +61,7 @@ export class ListsListModel {
} }
this._xLoading(replace) this._xLoading(replace)
try { try {
let res let res: GetLists.Response
if (this.source === 'my-modlists') { if (this.source === 'my-modlists') {
res = { res = {
success: true, success: true,
@ -170,7 +170,7 @@ async function fetchAllUserLists(
let cursor let cursor
for (let i = 0; i < 100; i++) { for (let i = 0; i < 100; i++) {
const res = await store.agent.app.bsky.graph.getLists({ const res: GetLists.Response = await store.agent.app.bsky.graph.getLists({
actor: did, actor: did,
cursor, cursor,
limit: 50, limit: 50,
@ -199,7 +199,8 @@ async function fetchAllMyMuteLists(
let cursor let cursor
for (let i = 0; i < 100; i++) { for (let i = 0; i < 100; i++) {
const res = await store.agent.app.bsky.graph.getListMutes({ const res: GetListMutes.Response =
await store.agent.app.bsky.graph.getListMutes({
cursor, cursor,
limit: 50, limit: 50,
}) })

View File

@ -8,6 +8,12 @@ import {ImageModel} from '../media/image'
import {ListModel} from '../content/list' import {ListModel} from '../content/list'
import {GalleryModel} from '../media/gallery' import {GalleryModel} from '../media/gallery'
export type ColorMode = 'system' | 'light' | 'dark'
export function isColorMode(v: unknown): v is ColorMode {
return v === 'system' || v === 'light' || v === 'dark'
}
export interface ConfirmModal { export interface ConfirmModal {
name: 'confirm' name: 'confirm'
title: string title: string
@ -189,7 +195,7 @@ export interface ComposerOpts {
} }
export class ShellUiModel { export class ShellUiModel {
colorMode = 'system' colorMode: ColorMode = 'system'
minimalShellMode = false minimalShellMode = false
isDrawerOpen = false isDrawerOpen = false
isDrawerSwipeDisabled = false isDrawerSwipeDisabled = false
@ -216,13 +222,13 @@ export class ShellUiModel {
hydrate(v: unknown) { hydrate(v: unknown) {
if (isObj(v)) { if (isObj(v)) {
if (hasProp(v, 'colorMode') && typeof v.colorMode === 'string') { if (hasProp(v, 'colorMode') && isColorMode(v.colorMode)) {
this.colorMode = v.colorMode this.colorMode = v.colorMode
} }
} }
} }
setColorMode(mode: string) { setColorMode(mode: ColorMode) {
this.colorMode = mode this.colorMode = mode
} }

View File

@ -1,5 +1,6 @@
import {useState, useEffect} from 'react' import {useState, useEffect} from 'react'
import {useStores} from 'state/index' import {useStores} from 'state/index'
import {ImageModel} from 'state/models/media/image'
import * as apilib from 'lib/api/index' import * as apilib from 'lib/api/index'
import {getLinkMeta} from 'lib/link-meta/link-meta' import {getLinkMeta} from 'lib/link-meta/link-meta'
import {getPostAsQuote, getFeedAsEmbed} from 'lib/link-meta/bsky' import {getPostAsQuote, getFeedAsEmbed} from 'lib/link-meta/bsky'
@ -90,7 +91,9 @@ export function useExternalLinkFetch({
setExtLink({ setExtLink({
...extLink, ...extLink,
isLoading: false, // done isLoading: false, // done
localThumb, localThumb: localThumb
? new ImageModel(store, localThumb)
: undefined,
}) })
}) })
return cleanup return cleanup

View File

@ -27,14 +27,14 @@ export const Lightbox = observer(function Lightbox() {
} }
let altText = '' let altText = ''
let uri let uri = ''
if (lightbox.name === 'images') { if (lightbox.name === 'images') {
const opts = store.shell.activeLightbox as models.ImagesLightbox const opts = lightbox as models.ImagesLightbox
uri = opts.images[imageIndex].uri uri = opts.images[imageIndex].uri
altText = opts.images[imageIndex].alt altText = opts.images[imageIndex].alt || ''
} else if (store.shell.activeLightbox.name === 'profile-image') { } else if (lightbox.name === 'profile-image') {
const opts = store.shell.activeLightbox as models.ProfileImageLightbox const opts = lightbox as models.ProfileImageLightbox
uri = opts.profileView.avatar uri = opts.profileView.avatar || ''
} }
return ( return (

View File

@ -44,11 +44,11 @@ export function Component({
const {track} = useAnalytics() const {track} = useAnalytics()
const [isProcessing, setProcessing] = useState<boolean>(false) const [isProcessing, setProcessing] = useState<boolean>(false)
const [name, setName] = useState<string>(list?.list.name || '') const [name, setName] = useState<string>(list?.list?.name || '')
const [description, setDescription] = useState<string>( const [description, setDescription] = useState<string>(
list?.list.description || '', list?.list?.description || '',
) )
const [avatar, setAvatar] = useState<string | undefined>(list?.list.avatar) const [avatar, setAvatar] = useState<string | undefined>(list?.list?.avatar)
const [newAvatar, setNewAvatar] = useState<RNImage | undefined | null>() const [newAvatar, setNewAvatar] = useState<RNImage | undefined | null>()
const onPressCancel = useCallback(() => { const onPressCancel = useCallback(() => {
@ -59,7 +59,7 @@ export function Component({
async (img: RNImage | null) => { async (img: RNImage | null) => {
if (!img) { if (!img) {
setNewAvatar(null) setNewAvatar(null)
setAvatar(null) setAvatar(undefined)
return return
} }
track('CreateMuteList:AvatarSelected') track('CreateMuteList:AvatarSelected')

View File

@ -36,7 +36,7 @@ export const Component = observer(
const pal = usePalette('default') const pal = usePalette('default')
const palPrimary = usePalette('primary') const palPrimary = usePalette('primary')
const palInverted = usePalette('inverted') const palInverted = usePalette('inverted')
const [selected, setSelected] = React.useState([]) const [selected, setSelected] = React.useState<string[]>([])
const listsList: ListsListModel = React.useMemo( const listsList: ListsListModel = React.useMemo(
() => new ListsListModel(store, store.me.did), () => new ListsListModel(store, store.me.did),

View File

@ -1,4 +1,4 @@
import React from 'react' import * as React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {observer} from 'mobx-react-lite' import {observer} from 'mobx-react-lite'
import {AppBskyActorDefs} from '@atproto/api' import {AppBskyActorDefs} from '@atproto/api'
@ -32,7 +32,9 @@ export const ProfileCard = observer(
noBorder?: boolean noBorder?: boolean
followers?: AppBskyActorDefs.ProfileView[] | undefined followers?: AppBskyActorDefs.ProfileView[] | undefined
overrideModeration?: boolean overrideModeration?: boolean
renderButton?: (profile: AppBskyActorDefs.ProfileViewBasic) => JSX.Element renderButton?: (
profile: AppBskyActorDefs.ProfileViewBasic,
) => React.ReactNode
}) => { }) => {
const store = useStores() const store = useStores()
const pal = usePalette('default') const pal = usePalette('default')

View File

@ -587,9 +587,9 @@ const styles = StyleSheet.create({
// Word wrapping appears fine on // Word wrapping appears fine on
// mobile but overflows on desktop // mobile but overflows on desktop
handle: isNative handle: isNative
? undefined ? {}
: { : {
// eslint-disable-next-line // @ts-ignore web only -prf
wordBreak: 'break-all', wordBreak: 'break-all',
}, },

View File

@ -15,6 +15,7 @@ export const BlurView = ({
}: React.PropsWithChildren<BlurViewProps>) => { }: React.PropsWithChildren<BlurViewProps>) => {
// @ts-ignore using an RNW-specific attribute here -prf // @ts-ignore using an RNW-specific attribute here -prf
let blur = `blur(${blurAmount || 10}px` let blur = `blur(${blurAmount || 10}px`
// @ts-ignore using an RNW-specific attribute here -prf
style = addStyle(style, {backdropFilter: blur, WebkitBackdropFilter: blur}) style = addStyle(style, {backdropFilter: blur, WebkitBackdropFilter: blur})
if (blurType === 'dark') { if (blurType === 'dark') {
style = addStyle(style, styles.dark) style = addStyle(style, styles.dark)

View File

@ -1,4 +1,4 @@
import React from 'react' import * as React from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import {usePalette} from 'lib/hooks/usePalette' import {usePalette} from 'lib/hooks/usePalette'
import {Text} from './text/Text' import {Text} from './text/Text'
@ -10,6 +10,15 @@ import {isDesktopWeb} from 'platform/detection'
* DSL. See for instance /locale/en/privacy-policy.tsx * DSL. See for instance /locale/en/privacy-policy.tsx
*/ */
interface IsChildProps {
isChild?: boolean
}
// type ReactNodeWithIsChildProp =
// | React.ReactElement<IsChildProps>
// | React.ReactElement<IsChildProps>[]
// | React.ReactNode
export function H1({children}: React.PropsWithChildren<{}>) { export function H1({children}: React.PropsWithChildren<{}>) {
const pal = usePalette('default') const pal = usePalette('default')
return ( return (
@ -55,10 +64,7 @@ export function P({children}: React.PropsWithChildren<{}>) {
) )
} }
export function UL({ export function UL({children, isChild}: React.PropsWithChildren<IsChildProps>) {
children,
isChild,
}: React.PropsWithChildren<{isChild: boolean}>) {
return ( return (
<View style={[styles.ul, isChild && styles.ulChild]}> <View style={[styles.ul, isChild && styles.ulChild]}>
{markChildProps(children)} {markChildProps(children)}
@ -66,10 +72,7 @@ export function UL({
) )
} }
export function OL({ export function OL({children, isChild}: React.PropsWithChildren<IsChildProps>) {
children,
isChild,
}: React.PropsWithChildren<{isChild: boolean}>) {
return ( return (
<View style={[styles.ol, isChild && styles.olChild]}> <View style={[styles.ol, isChild && styles.olChild]}>
{markChildProps(children)} {markChildProps(children)}
@ -122,10 +125,13 @@ export function EM({children}: React.PropsWithChildren<{}>) {
) )
} }
function markChildProps(children) { function markChildProps(children: React.ReactNode) {
return React.Children.map(children, child => { return React.Children.map(children, child => {
if (React.isValidElement(child)) { if (React.isValidElement(child)) {
return React.cloneElement(child, {isChild: true}) return React.cloneElement<IsChildProps>(
child as React.ReactElement<IsChildProps>,
{isChild: true},
)
} }
return child return child
}) })

View File

@ -70,7 +70,9 @@ export function UserInfoText({
numberOfLines={1} numberOfLines={1}
href={`/profile/${profile.handle}`} href={`/profile/${profile.handle}`}
text={`${prefix || ''}${sanitizeDisplayName( text={`${prefix || ''}${sanitizeDisplayName(
profile[attr] || profile.handle, typeof profile[attr] === 'string' && profile[attr]
? (profile[attr] as string)
: profile.handle,
)}`} )}`}
/> />
) )

View File

@ -38,6 +38,7 @@ import {NavigationProp} from 'lib/routes/types'
import {isDesktopWeb} from 'platform/detection' import {isDesktopWeb} from 'platform/detection'
import {pluralize} from 'lib/strings/helpers' import {pluralize} from 'lib/strings/helpers'
import {formatCount} from 'view/com/util/numeric/format' import {formatCount} from 'view/com/util/numeric/format'
import {isColorMode} from 'state/models/ui/shell'
type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'Settings'>
export const SettingsScreen = withAuthRequired( export const SettingsScreen = withAuthRequired(
@ -299,20 +300,26 @@ export const SettingsScreen = withAuthRequired(
value="system" value="system"
label="System" label="System"
left left
onChange={(v: string) => store.shell.setColorMode(v)} onChange={(v: string) =>
store.shell.setColorMode(isColorMode(v) ? v : 'system')
}
/> />
<SelectableBtn <SelectableBtn
current={store.shell.colorMode} current={store.shell.colorMode}
value="light" value="light"
label="Light" label="Light"
onChange={(v: string) => store.shell.setColorMode(v)} onChange={(v: string) =>
store.shell.setColorMode(isColorMode(v) ? v : 'system')
}
/> />
<SelectableBtn <SelectableBtn
current={store.shell.colorMode} current={store.shell.colorMode}
value="dark" value="dark"
label="Dark" label="Dark"
right right
onChange={(v: string) => store.shell.setColorMode(v)} onChange={(v: string) =>
store.shell.setColorMode(isColorMode(v) ? v : 'system')
}
/> />
</View> </View>
</View> </View>

View File

@ -34,7 +34,7 @@ import {
SatelliteDishIconSolid, SatelliteDishIconSolid,
} from 'lib/icons' } from 'lib/icons'
import {getCurrentRoute, isTab, isStateAtTabRoot} from 'lib/routes/helpers' import {getCurrentRoute, isTab, isStateAtTabRoot} from 'lib/routes/helpers'
import {NavigationProp} from 'lib/routes/types' import {NavigationProp, CommonNavigatorParams} from 'lib/routes/types'
import {router} from '../../../routes' import {router} from '../../../routes'
const ProfileCard = observer(() => { const ProfileCard = observer(() => {
@ -100,7 +100,8 @@ const NavItem = observer(
let isCurrent = let isCurrent =
currentRouteInfo.name === 'Profile' currentRouteInfo.name === 'Profile'
? isTab(currentRouteInfo.name, pathName) && ? isTab(currentRouteInfo.name, pathName) &&
currentRouteInfo.params.name === store.me.handle (currentRouteInfo.params as CommonNavigatorParams['Profile']).name ===
store.me.handle
: isTab(currentRouteInfo.name, pathName) : isTab(currentRouteInfo.name, pathName)
const {onPress} = useLinkProps({to: href}) const {onPress} = useLinkProps({to: href})
const onPressWrapped = React.useCallback( const onPressWrapped = React.useCallback(
@ -122,6 +123,7 @@ const NavItem = observer(
<PressableWithHover <PressableWithHover
style={styles.navItemWrapper} style={styles.navItemWrapper}
hoverStyle={pal.viewLight} hoverStyle={pal.viewLight}
// @ts-ignore the function signature differs on web -prf
onPress={onPressWrapped} onPress={onPressWrapped}
// @ts-ignore web only -prf // @ts-ignore web only -prf
href={href} href={href}

View File

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["__e2e__", "dist"],
}