Merge remote-tracking branch 'origin/main' into 3p-moderators

* origin/main: (69 commits)
  Update .po files
  use `showControls` to show/hide live text icon on ios (#2982)
  Fix dim mode unread notif color
  Make dim theme dim (#2966)
  Add handle validation to create account UI (#2959)
  Normalize relative day (#2874)
  increase timeout to 15s (#2958)
  use `useOpenLink` hook for links in ALF (#2975)
  Rename Home Feed Prefs to Following Feed Prefs (#2965)
  Refactor feed header components (#2964)
  patch react-navigation to fix history bug (#2955)
  Use EAS managed build number, run build/submit on GH Actions (#2841)
  Fix `numberOfLines` not updating on iOS 15 (#2956)
  Fix UITextView line height adjustment for DynamicType, always use the max width for the view (#2916)
  Navigate back from a deleted post's route (#2948)
  Add optional close callback to Dialog (#2947)
  Fix flash when pressing into just-created post (#2945)
  Last usage (#2944)
  Update blogpost URL in ExportCarDialog.tsx (#2939)
  Prefer full posts for post thread placeholder (#2943)
  ...
This commit is contained in:
Eric Bailey
2024-02-26 14:50:43 -06:00
143 changed files with 16079 additions and 9867 deletions
+4 -2
View File
@@ -55,6 +55,8 @@ export type ButtonState = {
disabled: boolean
}
export type ButtonContext = VariantProps & ButtonState
export type ButtonProps = Pick<
PressableProps,
'disabled' | 'onPress' | 'testID'
@@ -67,7 +69,7 @@ export type ButtonProps = Pick<
children:
| React.ReactNode
| string
| ((state: VariantProps & ButtonState) => React.ReactNode | string)
| ((context: ButtonContext) => React.ReactNode | string)
}
export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
@@ -351,7 +353,7 @@ export function Button({
}
}, [variant, color])
const context = React.useMemo(
const context = React.useMemo<ButtonContext>(
() => ({
...state,
variant,
+7 -3
View File
@@ -1,7 +1,11 @@
import React from 'react'
import {useDialogStateContext} from '#/state/dialogs'
import {DialogContextProps, DialogControlProps} from '#/components/Dialog/types'
import {
DialogContextProps,
DialogControlProps,
DialogOuterProps,
} from '#/components/Dialog/types'
export const Context = React.createContext<DialogContextProps>({
close: () => {},
@@ -11,7 +15,7 @@ export function useDialogContext() {
return React.useContext(Context)
}
export function useDialogControl() {
export function useDialogControl(): DialogOuterProps['control'] {
const id = React.useId()
const control = React.useRef<DialogControlProps>({
open: () => {},
@@ -30,6 +34,6 @@ export function useDialogControl() {
return {
ref: control,
open: () => control.current.open(),
close: () => control.current.close(),
close: cb => control.current.close(cb),
}
}
+78 -52
View File
@@ -8,7 +8,7 @@ import BottomSheet, {
} from '@gorhom/bottom-sheet'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {useTheme, atoms as a} from '#/alf'
import {useTheme, atoms as a, flatten} from '#/alf'
import {Portal} from '#/components/Portal'
import {createInput} from '#/components/forms/TextField'
@@ -29,19 +29,36 @@ export function Outer({
control,
onClose,
nativeOptions,
defaultOpen,
}: React.PropsWithChildren<DialogOuterProps>) {
const t = useTheme()
const sheet = React.useRef<BottomSheet>(null)
const sheetOptions = nativeOptions?.sheet || {}
const hasSnapPoints = !!sheetOptions.snapPoints
const insets = useSafeAreaInsets()
const closeCallback = React.useRef<() => void>()
const open = React.useCallback<DialogControlProps['open']>(({index} = {}) => {
sheet.current?.snapToIndex(index || 0)
}, [])
/*
* Used to manage open/closed, but index is otherwise handled internally by `BottomSheet`
*/
const [openIndex, setOpenIndex] = React.useState(-1)
const close = React.useCallback(() => {
/*
* `openIndex` is the index of the snap point to open the bottom sheet to. If >0, the bottom sheet is open.
*/
const isOpen = openIndex > -1
const open = React.useCallback<DialogControlProps['open']>(
({index} = {}) => {
// can be set to any index of `snapPoints`, but `0` is the first i.e. "open"
setOpenIndex(index || 0)
},
[setOpenIndex],
)
const close = React.useCallback<DialogControlProps['close']>(cb => {
if (cb) {
closeCallback.current = cb
}
sheet.current?.close()
}, [])
@@ -57,60 +74,66 @@ export function Outer({
const onChange = React.useCallback(
(index: number) => {
if (index === -1) {
closeCallback.current?.()
closeCallback.current = undefined
onClose?.()
setOpenIndex(-1)
}
},
[onClose],
[onClose, setOpenIndex],
)
const context = React.useMemo(() => ({close}), [close])
return (
<Portal>
<BottomSheet
enableDynamicSizing={!hasSnapPoints}
enablePanDownToClose
keyboardBehavior="interactive"
android_keyboardInputMode="adjustResize"
keyboardBlurBehavior="restore"
topInset={insets.top}
{...sheetOptions}
ref={sheet}
index={defaultOpen ? 0 : -1}
backgroundStyle={{backgroundColor: 'transparent'}}
backdropComponent={props => (
<BottomSheetBackdrop
opacity={0.4}
appearsOnIndex={0}
disappearsOnIndex={-1}
{...props}
/>
)}
handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
handleStyle={{display: 'none'}}
onChange={onChange}>
<Context.Provider value={context}>
<View
style={[
a.absolute,
a.inset_0,
t.atoms.bg,
{
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
height: Dimensions.get('window').height * 2,
},
]}
/>
{children}
</Context.Provider>
</BottomSheet>
</Portal>
isOpen && (
<Portal>
<BottomSheet
enableDynamicSizing={!hasSnapPoints}
enablePanDownToClose
keyboardBehavior="interactive"
android_keyboardInputMode="adjustResize"
keyboardBlurBehavior="restore"
topInset={insets.top}
{...sheetOptions}
snapPoints={sheetOptions.snapPoints || ['100%']}
ref={sheet}
index={openIndex}
backgroundStyle={{backgroundColor: 'transparent'}}
backdropComponent={props => (
<BottomSheetBackdrop
opacity={0.4}
appearsOnIndex={0}
disappearsOnIndex={-1}
{...props}
style={[flatten(props.style), t.atoms.bg_contrast_300]}
/>
)}
handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
handleStyle={{display: 'none'}}
onChange={onChange}>
<Context.Provider value={context}>
<View
style={[
a.absolute,
a.inset_0,
t.atoms.bg,
{
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
height: Dimensions.get('window').height * 2,
},
]}
/>
{children}
</Context.Provider>
</BottomSheet>
</Portal>
)
)
}
// TODO a11y props here, or is that handled by the sheet?
export function Inner(props: DialogInnerProps) {
export function Inner({children, style}: DialogInnerProps) {
const insets = useSafeAreaInsets()
return (
<BottomSheetView
@@ -122,13 +145,14 @@ export function Inner(props: DialogInnerProps) {
borderTopRightRadius: 40,
paddingBottom: insets.bottom + a.pb_5xl.paddingBottom,
},
flatten(style),
]}>
{props.children}
{children}
</BottomSheetView>
)
}
export function ScrollableInner(props: DialogInnerProps) {
export function ScrollableInner({children, style}: DialogInnerProps) {
const insets = useSafeAreaInsets()
return (
<BottomSheetScrollView
@@ -137,13 +161,15 @@ export function ScrollableInner(props: DialogInnerProps) {
style={[
a.flex_1, // main diff is this
a.p_xl,
a.h_full,
{
paddingTop: 40,
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
},
flatten(style),
]}>
{props.children}
{children}
<View style={{height: insets.bottom + a.pt_5xl.paddingTop}} />
</BottomSheetScrollView>
)
+3 -4
View File
@@ -5,7 +5,7 @@ import Animated, {FadeInDown, FadeIn} from 'react-native-reanimated'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useTheme, atoms as a, useBreakpoints, web} from '#/alf'
import {useTheme, atoms as a, useBreakpoints, web, flatten} from '#/alf'
import {Portal} from '#/components/Portal'
import {DialogOuterProps, DialogInnerProps} from '#/components/Dialog/types'
@@ -23,12 +23,11 @@ export function Outer({
children,
control,
onClose,
defaultOpen,
}: React.PropsWithChildren<DialogOuterProps>) {
const {_} = useLingui()
const t = useTheme()
const {gtMobile} = useBreakpoints()
const [isOpen, setIsOpen] = React.useState(defaultOpen)
const [isOpen, setIsOpen] = React.useState(false)
const [isVisible, setIsVisible] = React.useState(true)
const open = React.useCallback(() => {
@@ -159,7 +158,7 @@ export function Inner({
shadowOpacity: t.name === 'light' ? 0.1 : 0.4,
shadowRadius: 30,
},
...(Array.isArray(style) ? style : [style || {}]),
flatten(style),
]}>
{children}
</Animated.View>
+15 -11
View File
@@ -1,27 +1,34 @@
import React from 'react'
import type {ViewStyle, AccessibilityProps} from 'react-native'
import type {AccessibilityProps} from 'react-native'
import {BottomSheetProps} from '@gorhom/bottom-sheet'
import {ViewStyleProp} from '#/alf'
type A11yProps = Required<AccessibilityProps>
export type DialogContextProps = {
close: () => void
}
export type DialogControlOpenOptions = {index?: number}
export type DialogControlOpenOptions = {
/**
* NATIVE ONLY
*
* Optional index of the snap point to open the bottom sheet to. Defaults to
* 0, which is the first snap point (i.e. "open").
*/
index?: number
}
export type DialogControlProps = {
open: (options?: DialogControlOpenOptions) => void
close: () => void
close: (callback?: () => void) => void
}
export type DialogOuterProps = {
defaultOpen?: boolean
control: {
ref: React.RefObject<DialogControlProps>
open: (index?: number) => void
close: () => void
}
} & DialogControlProps
onClose?: () => void
nativeOptions?: {
sheet?: Omit<BottomSheetProps, 'children'>
@@ -29,10 +36,7 @@ export type DialogOuterProps = {
webOptions?: {}
}
type DialogInnerPropsBase<T> = React.PropsWithChildren<{
style?: ViewStyle
}> &
T
type DialogInnerPropsBase<T> = React.PropsWithChildren<ViewStyleProp> & T
export type DialogInnerProps =
| DialogInnerPropsBase<{
label?: undefined
+51
View File
@@ -0,0 +1,51 @@
import React from 'react'
import {View} from 'react-native'
import {
useTheme,
atoms as a,
ViewStyleProp,
TextStyleProp,
flatten,
} from '#/alf'
import {Growth_Stroke2_Corner0_Rounded as Growth} from '#/components/icons/Growth'
import {Props} from '#/components/icons/common'
export function IconCircle({
icon: Icon,
size = 'xl',
style,
iconStyle,
}: ViewStyleProp & {
icon: typeof Growth
size?: Props['size']
iconStyle?: TextStyleProp['style']
}) {
const t = useTheme()
return (
<View
style={[
a.justify_center,
a.align_center,
a.rounded_full,
{
width: size === 'lg' ? 52 : 64,
height: size === 'lg' ? 52 : 64,
backgroundColor:
t.name === 'light' ? t.palette.primary_50 : t.palette.primary_950,
},
flatten(style),
]}>
<Icon
size={size}
style={[
{
color: t.palette.primary_500,
},
flatten(iconStyle),
]}
/>
</View>
)
}
+79 -63
View File
@@ -1,9 +1,5 @@
import React from 'react'
import {
GestureResponderEvent,
Linking,
TouchableWithoutFeedback,
} from 'react-native'
import {GestureResponderEvent} from 'react-native'
import {
useLinkProps,
useNavigation,
@@ -23,7 +19,8 @@ import {
} from '#/lib/strings/url-helpers'
import {useModalControls} from '#/state/modals'
import {router} from '#/routes'
import {Text} from '#/components/Typography'
import {Text, TextProps} from '#/components/Typography'
import {useOpenLink} from 'state/preferences/in-app-browser'
/**
* Only available within a `Link`, since that inherits from `Button`.
@@ -37,6 +34,11 @@ type BaseLinkProps = Pick<
> & {
testID?: string
/**
* Label for a11y. Defaults to the href.
*/
label?: string
/**
* The React Navigation `StackAction` to perform when the link is pressed.
*/
@@ -50,11 +52,17 @@ type BaseLinkProps = Pick<
warnOnMismatchingTextChild?: boolean
/**
* Callback for when the link is pressed.
* Callback for when the link is pressed. Prevent default and return `false`
* to exit early and prevent navigation.
*
* DO NOT use this for navigation, that's what the `to` prop is for.
*/
onPress?: (e: GestureResponderEvent) => void
onPress?: (e: GestureResponderEvent) => void | false
/**
* Web-only attribute. Sets `download` attr on web.
*/
download?: string
}
export function useLink({
@@ -73,10 +81,13 @@ export function useLink({
})
const isExternal = isExternalUrl(href)
const {openModal, closeModal} = useModalControls()
const openLink = useOpenLink()
const onPress = React.useCallback(
(e: GestureResponderEvent) => {
outerOnPress?.(e)
const exitEarlyIfFalse = outerOnPress?.(e)
if (exitEarlyIfFalse === false) return
const requiresWarning = Boolean(
warnOnMismatchingTextChild &&
@@ -97,7 +108,7 @@ export function useLink({
e.preventDefault()
if (isExternal) {
Linking.openURL(href)
openLink(href)
} else {
/**
* A `GestureResponderEvent`, but cast to `any` to avoid using a bunch
@@ -115,7 +126,7 @@ export function useLink({
href.startsWith('http') ||
href.startsWith('mailto')
) {
Linking.openURL(href)
openLink(href)
} else {
closeModal() // close any active modals
@@ -136,15 +147,16 @@ export function useLink({
}
},
[
href,
isExternal,
warnOnMismatchingTextChild,
navigation,
action,
displayText,
closeModal,
openModal,
outerOnPress,
warnOnMismatchingTextChild,
displayText,
isExternal,
href,
openModal,
openLink,
closeModal,
action,
navigation,
],
)
@@ -156,12 +168,7 @@ export function useLink({
}
export type LinkProps = Omit<BaseLinkProps, 'warnOnMismatchingTextChild'> &
Omit<ButtonProps, 'onPress' | 'disabled' | 'label'> & {
/**
* Label for a11y. Defaults to the href.
*/
label?: string
}
Omit<ButtonProps, 'onPress' | 'disabled' | 'label'>
/**
* A interactive element that renders as a `<a>` tag on the web. On mobile it
@@ -176,6 +183,7 @@ export function Link({
to,
action = 'push',
onPress: outerOnPress,
download,
...rest
}: LinkProps) {
const {href, isExternal, onPress} = useLink({
@@ -193,14 +201,15 @@ export function Link({
role="link"
accessibilityRole="link"
href={href}
onPress={onPress}
onPress={download ? undefined : onPress}
{...web({
hrefAttrs: {
target: isExternal ? 'blank' : undefined,
target: download ? undefined : isExternal ? 'blank' : undefined,
rel: isExternal ? 'noopener noreferrer' : undefined,
download,
},
dataSet: {
// default to no underline, apply this ourselves
// no underline, only `InlineLink` has underlines
noUnderline: '1',
},
})}>
@@ -210,13 +219,7 @@ export function Link({
}
export type InlineLinkProps = React.PropsWithChildren<
BaseLinkProps &
TextStyleProp & {
/**
* Label for a11y. Defaults to the href.
*/
label?: string
}
BaseLinkProps & TextStyleProp & Pick<TextProps, 'selectable'>
>
export function InlineLink({
@@ -226,6 +229,8 @@ export function InlineLink({
warnOnMismatchingTextChild,
style,
onPress: outerOnPress,
download,
selectable,
...rest
}: InlineLinkProps) {
const t = useTheme()
@@ -237,44 +242,55 @@ export function InlineLink({
warnOnMismatchingTextChild,
onPress: outerOnPress,
})
const {
state: hovered,
onIn: onHoverIn,
onOut: onHoverOut,
} = useInteractionState()
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
const {
state: pressed,
onIn: onPressIn,
onOut: onPressOut,
} = useInteractionState()
const flattenedStyle = flatten(style)
return (
<TouchableWithoutFeedback
accessibilityRole="button"
onPress={onPress}
<Text
selectable={selectable}
label={href}
{...rest}
style={[
{color: t.palette.primary_500},
(hovered || focused || pressed) && {
...web({outline: 0}),
textDecorationLine: 'underline',
textDecorationColor: flattenedStyle.color ?? t.palette.primary_500,
},
flattenedStyle,
]}
role="link"
onPress={download ? undefined : onPress}
onPressIn={onPressIn}
onPressOut={onPressOut}
onFocus={onFocus}
onBlur={onBlur}>
<Text
label={href}
{...rest}
style={[
{color: t.palette.primary_500},
(focused || pressed) && {
outline: 0,
textDecorationLine: 'underline',
textDecorationColor: t.palette.primary_500,
},
flatten(style),
]}
role="link"
accessibilityRole="link"
href={href}
{...web({
hrefAttrs: {
target: isExternal ? 'blank' : undefined,
rel: isExternal ? 'noopener noreferrer' : undefined,
},
})}>
{children}
</Text>
</TouchableWithoutFeedback>
onBlur={onBlur}
onMouseEnter={onHoverIn}
onMouseLeave={onHoverOut}
accessibilityRole="link"
href={href}
{...web({
hrefAttrs: {
target: download ? undefined : isExternal ? 'blank' : undefined,
rel: isExternal ? 'noopener noreferrer' : undefined,
download,
},
dataSet: {
// default to no underline, apply this ourselves
noUnderline: '1',
},
})}>
{children}
</Text>
)
}
+7 -2
View File
@@ -7,7 +7,7 @@ import Animated, {
withTiming,
} from 'react-native-reanimated'
import {atoms as a, useTheme} from '#/alf'
import {atoms as a, useTheme, flatten} from '#/alf'
import {Props, useCommonSVGProps} from '#/components/icons/common'
import {Loader_Stroke2_Corner0_Rounded as Icon} from '#/components/icons/Loader'
@@ -38,7 +38,12 @@ export function Loader(props: Props) {
]}>
<Icon
{...props}
style={[a.absolute, a.inset_0, props.style, t.atoms.text_contrast_high]}
style={[
a.absolute,
a.inset_0,
t.atoms.text_contrast_high,
flatten(props.style),
]}
/>
</Animated.View>
)
+1 -1
View File
@@ -41,7 +41,7 @@ export function Outer({
<Dialog.Inner
accessibilityLabelledBy={titleId}
accessibilityDescribedBy={descriptionId}
style={{width: 'auto', maxWidth: 400}}>
style={[{width: 'auto', maxWidth: 400}]}>
{children}
</Dialog.Inner>
</Context.Provider>
+17 -10
View File
@@ -1,9 +1,9 @@
import React from 'react'
import {RichText as RichTextAPI, AppBskyRichtextFacet} from '@atproto/api'
import {atoms as a, TextStyleProp} from '#/alf'
import {atoms as a, TextStyleProp, flatten} from '#/alf'
import {InlineLink} from '#/components/Link'
import {Text} from '#/components/Typography'
import {Text, TextProps} from '#/components/Typography'
import {toShortUrl} from 'lib/strings/url-helpers'
import {getAgent} from '#/state/session'
@@ -16,18 +16,20 @@ export function RichText({
numberOfLines,
disableLinks,
resolveFacets = false,
}: TextStyleProp & {
value: RichTextAPI | string
testID?: string
numberOfLines?: number
disableLinks?: boolean
resolveFacets?: boolean
}) {
selectable,
}: TextStyleProp &
Pick<TextProps, 'selectable'> & {
value: RichTextAPI | string
testID?: string
numberOfLines?: number
disableLinks?: boolean
resolveFacets?: boolean
}) {
const detected = React.useRef(false)
const [richText, setRichText] = React.useState<RichTextAPI>(() =>
value instanceof RichTextAPI ? value : new RichTextAPI({text: value}),
)
const styles = [a.leading_normal, style]
const styles = [a.leading_snug, flatten(style)]
React.useEffect(() => {
if (!resolveFacets) return
@@ -50,6 +52,7 @@ export function RichText({
if (text.length <= 5 && /^\p{Extended_Pictographic}+$/u.test(text)) {
return (
<Text
selectable={selectable}
testID={testID}
style={[
{
@@ -65,6 +68,7 @@ export function RichText({
}
return (
<Text
selectable={selectable}
testID={testID}
style={styles}
numberOfLines={numberOfLines}
@@ -88,6 +92,7 @@ export function RichText({
) {
els.push(
<InlineLink
selectable={selectable}
key={key}
to={`/profile/${mention.did}`}
style={[...styles, {pointerEvents: 'auto'}]}
@@ -102,6 +107,7 @@ export function RichText({
} else {
els.push(
<InlineLink
selectable={selectable}
key={key}
to={link.uri}
style={[...styles, {pointerEvents: 'auto'}]}
@@ -120,6 +126,7 @@ export function RichText({
return (
<Text
selectable={selectable}
testID={testID}
style={styles}
numberOfLines={numberOfLines}
+19 -19
View File
@@ -1,7 +1,16 @@
import React from 'react'
import {Text as RNText, TextStyle, TextProps} from 'react-native'
import {Text as RNText, TextStyle, TextProps as RNTextProps} from 'react-native'
import {UITextView} from 'react-native-ui-text-view'
import {useTheme, atoms, web, flatten} from '#/alf'
import {isIOS} from '#/platform/detection'
export type TextProps = RNTextProps & {
/**
* Lets the user select text, to use the native copy and paste functionality.
*/
selectable?: boolean
}
/**
* Util to calculate lineHeight from a text size atom and a leading atom
@@ -44,27 +53,24 @@ function normalizeTextStyles(styles: TextStyle[]) {
/**
* Our main text component. Use this most of the time.
*/
export function Text({style, ...rest}: TextProps) {
export function Text({style, selectable, ...rest}: TextProps) {
const t = useTheme()
const s = normalizeTextStyles([atoms.text_sm, t.atoms.text, flatten(style)])
return <RNText style={s} {...rest} />
return selectable && isIOS ? (
<UITextView style={s} {...rest} />
) : (
<RNText selectable={selectable} style={s} {...rest} />
)
}
export function createHeadingElement({level}: {level: number}) {
return function HeadingElement({style, ...rest}: TextProps) {
const t = useTheme()
const attr =
web({
role: 'heading',
'aria-level': level,
}) || {}
return (
<RNText
{...attr}
{...rest}
style={normalizeTextStyles([t.atoms.text, flatten(style)])}
/>
)
return <Text {...attr} {...rest} style={style} />
}
}
@@ -78,21 +84,15 @@ export const H4 = createHeadingElement({level: 4})
export const H5 = createHeadingElement({level: 5})
export const H6 = createHeadingElement({level: 6})
export function P({style, ...rest}: TextProps) {
const t = useTheme()
const attr =
web({
role: 'paragraph',
}) || {}
return (
<RNText
<Text
{...attr}
{...rest}
style={normalizeTextStyles([
atoms.text_md,
atoms.leading_normal,
t.atoms.text,
flatten(style),
])}
style={[atoms.text_md, atoms.leading_normal, flatten(style)]}
/>
)
}
@@ -98,7 +98,7 @@ export function DateField({
timeZoneName={'Etc/UTC'}
display="spinner"
// @ts-ignore applies in iOS only -prf
themeVariant={t.name === 'dark' ? 'dark' : 'light'}
themeVariant={t.name === 'light' ? 'light' : 'dark'}
value={new Date(value)}
onChange={onChangeInternal}
/>
+1 -1
View File
@@ -47,7 +47,7 @@ export function DateField({
mode="date"
timeZoneName={'Etc/UTC'}
display="spinner"
themeVariant={t.name === 'dark' ? 'dark' : 'light'}
themeVariant={t.name === 'light' ? 'light' : 'dark'}
value={new Date(value)}
onChange={onChangeInternal}
/>
+18 -42
View File
@@ -5,15 +5,13 @@ import {
TextInputProps,
TextStyle,
ViewStyle,
Pressable,
StyleSheet,
AccessibilityProps,
} from 'react-native'
import {HITSLOP_20} from 'lib/constants'
import {isWeb} from '#/platform/detection'
import {useTheme, atoms as a, web, tokens, android, flatten} from '#/alf'
import {Text, leading} from '#/components/Typography'
import {useTheme, atoms as a, web, tokens, android} from '#/alf'
import {Text} from '#/components/Typography'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {Props as SVGIconProps} from '#/components/icons/common'
@@ -41,7 +39,6 @@ export type RootProps = React.PropsWithChildren<{isInvalid?: boolean}>
export function Root({children, isInvalid = false}: RootProps) {
const inputRef = React.useRef<TextInput>(null)
const rootRef = React.useRef<View>(null)
const {
state: hovered,
onIn: onHoverIn,
@@ -72,35 +69,17 @@ export function Root({children, isInvalid = false}: RootProps) {
],
)
React.useLayoutEffect(() => {
const root = rootRef.current
if (!root || !isWeb) return
// @ts-ignore web only
root.tabIndex = -1
}, [])
return (
<Context.Provider value={context}>
<Pressable
accessibilityRole="button"
ref={rootRef}
role="none"
style={[
a.flex_row,
a.align_center,
a.relative,
a.w_full,
a.px_md,
{
paddingVertical: 14,
},
]}
// onPressIn/out don't work on android web
onPress={() => inputRef.current?.focus()}
onHoverIn={onHoverIn}
onHoverOut={onHoverOut}>
<View
style={[a.flex_row, a.align_center, a.relative, a.w_full, a.px_md]}
{...web({
onClick: () => inputRef.current?.focus(),
onMouseOver: onHoverIn,
onMouseOut: onHoverOut,
})}>
{children}
</Pressable>
</View>
</Context.Provider>
)
}
@@ -149,7 +128,6 @@ export type InputProps = Omit<TextInputProps, 'value' | 'onChangeText'> & {
value: string
onChangeText: (value: string) => void
isInvalid?: boolean
disabled?: boolean
}
export function createInput(Component: typeof TextInput) {
@@ -188,7 +166,6 @@ export function createInput(Component: typeof TextInput) {
<Component
accessibilityHint={undefined}
{...rest}
aria-label={label}
accessibilityLabel={label}
ref={ctx.inputRef}
value={value}
@@ -205,17 +182,17 @@ export function createInput(Component: typeof TextInput) {
a.text_md,
t.atoms.text,
a.px_xs,
android({
paddingBottom: 2,
}),
{
lineHeight: rest.multiline
? leading(a.text_md, a.leading_normal)
: a.text_md.fontSize * 1.1875,
// paddingVertical doesn't work w/multiline - esb
paddingTop: 14,
paddingBottom: 14,
lineHeight: a.text_md.fontSize * 1.1875,
textAlignVertical: rest.multiline ? 'top' : undefined,
minHeight: rest.multiline ? 60 : undefined,
minHeight: rest.multiline ? 80 : undefined,
},
flatten(rest.style),
android({
paddingBottom: 16,
}),
]}
/>
@@ -317,7 +294,6 @@ export function Suffix({
const ctx = React.useContext(Context)
return (
<Text
aria-label={label}
accessibilityLabel={label}
accessibilityHint={accessibilityHint}
style={[