aa6aad652e
* thread preferences screen * minor tweaks * more spacing * replace gate with IS_INTERNAL * [Settings] Following feed prefs revamp (#5773) * gated new settings screen * Following feed prefs * Update src/screens/Settings/FollowingFeedPreferences.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/screens/Settings/FollowingFeedPreferences.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * replace pref following feed gate * Update src/screens/Settings/FollowingFeedPreferences.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * use "Experimental" as the header --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * [Settings] External media prefs revamp (#5774) * gated new settings screen * external media prefs revamp * replace gate ext media embeds * Update src/screens/Settings/ExternalMediaPreferences.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * add imports for translation * alternate list style on native --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * [Settings] Languages revamp (partial) (#5775) * language settings (lazy restyle) * replace gate * fix text determining flex space * [Settings] App passwords revamp (#5777) * rework app passwords screen * Apply surfdude's copy changes Thanks @surfdude29! Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * format * replace gate * use admonition for input error and animate --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * [Settings] Change handle dialog (#5781) * new change handle dialog * animations native only * overflow hidden on togglebutton animation * add a low-contrast border * extract out copybutton * finish change handle dialog * invalidate query on success * web fixes * error message for rate limit exceeded * typo * em dash! Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * another em dash Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * set maxwidth of suffixtext * Copy tweak Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * [Settings] Notifs settings revamp (#5884) * rename, move, and restyle notif settings * bold "experimental:" --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com>
352 lines
8.2 KiB
TypeScript
352 lines
8.2 KiB
TypeScript
import React from 'react'
|
|
import {
|
|
AccessibilityProps,
|
|
StyleSheet,
|
|
TextInput,
|
|
TextInputProps,
|
|
TextStyle,
|
|
View,
|
|
ViewStyle,
|
|
} from 'react-native'
|
|
|
|
import {HITSLOP_20} from '#/lib/constants'
|
|
import {mergeRefs} from '#/lib/merge-refs'
|
|
import {android, atoms as a, TextStyleProp, useTheme, web} from '#/alf'
|
|
import {useInteractionState} from '#/components/hooks/useInteractionState'
|
|
import {Props as SVGIconProps} from '#/components/icons/common'
|
|
import {Text} from '#/components/Typography'
|
|
|
|
const Context = React.createContext<{
|
|
inputRef: React.RefObject<TextInput> | null
|
|
isInvalid: boolean
|
|
hovered: boolean
|
|
onHoverIn: () => void
|
|
onHoverOut: () => void
|
|
focused: boolean
|
|
onFocus: () => void
|
|
onBlur: () => void
|
|
}>({
|
|
inputRef: null,
|
|
isInvalid: false,
|
|
hovered: false,
|
|
onHoverIn: () => {},
|
|
onHoverOut: () => {},
|
|
focused: false,
|
|
onFocus: () => {},
|
|
onBlur: () => {},
|
|
})
|
|
|
|
export type RootProps = React.PropsWithChildren<{isInvalid?: boolean}>
|
|
|
|
export function Root({children, isInvalid = false}: RootProps) {
|
|
const inputRef = React.useRef<TextInput>(null)
|
|
const {
|
|
state: hovered,
|
|
onIn: onHoverIn,
|
|
onOut: onHoverOut,
|
|
} = useInteractionState()
|
|
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
|
|
|
|
const context = React.useMemo(
|
|
() => ({
|
|
inputRef,
|
|
hovered,
|
|
onHoverIn,
|
|
onHoverOut,
|
|
focused,
|
|
onFocus,
|
|
onBlur,
|
|
isInvalid,
|
|
}),
|
|
[
|
|
inputRef,
|
|
hovered,
|
|
onHoverIn,
|
|
onHoverOut,
|
|
focused,
|
|
onFocus,
|
|
onBlur,
|
|
isInvalid,
|
|
],
|
|
)
|
|
|
|
return (
|
|
<Context.Provider value={context}>
|
|
<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}
|
|
</View>
|
|
</Context.Provider>
|
|
)
|
|
}
|
|
|
|
export function useSharedInputStyles() {
|
|
const t = useTheme()
|
|
return React.useMemo(() => {
|
|
const hover: ViewStyle[] = [
|
|
{
|
|
borderColor: t.palette.contrast_100,
|
|
},
|
|
]
|
|
const focus: ViewStyle[] = [
|
|
{
|
|
backgroundColor: t.palette.contrast_50,
|
|
borderColor: t.palette.primary_500,
|
|
},
|
|
]
|
|
const error: ViewStyle[] = [
|
|
{
|
|
backgroundColor: t.palette.negative_25,
|
|
borderColor: t.palette.negative_300,
|
|
},
|
|
]
|
|
const errorHover: ViewStyle[] = [
|
|
{
|
|
backgroundColor: t.palette.negative_25,
|
|
borderColor: t.palette.negative_500,
|
|
},
|
|
]
|
|
|
|
return {
|
|
chromeHover: StyleSheet.flatten(hover),
|
|
chromeFocus: StyleSheet.flatten(focus),
|
|
chromeError: StyleSheet.flatten(error),
|
|
chromeErrorHover: StyleSheet.flatten(errorHover),
|
|
}
|
|
}, [t])
|
|
}
|
|
|
|
export type InputProps = Omit<TextInputProps, 'value' | 'onChangeText'> & {
|
|
label: string
|
|
/**
|
|
* @deprecated Controlled inputs are *strongly* discouraged. Use `defaultValue` instead where possible.
|
|
*
|
|
* See https://github.com/facebook/react-native-website/pull/4247
|
|
*/
|
|
value?: string
|
|
onChangeText?: (value: string) => void
|
|
isInvalid?: boolean
|
|
inputRef?: React.RefObject<TextInput> | React.ForwardedRef<TextInput>
|
|
}
|
|
|
|
export function createInput(Component: typeof TextInput) {
|
|
return function Input({
|
|
label,
|
|
placeholder,
|
|
value,
|
|
onChangeText,
|
|
onFocus,
|
|
onBlur,
|
|
isInvalid,
|
|
inputRef,
|
|
style,
|
|
...rest
|
|
}: InputProps) {
|
|
const t = useTheme()
|
|
const ctx = React.useContext(Context)
|
|
const withinRoot = Boolean(ctx.inputRef)
|
|
|
|
const {chromeHover, chromeFocus, chromeError, chromeErrorHover} =
|
|
useSharedInputStyles()
|
|
|
|
if (!withinRoot) {
|
|
return (
|
|
<Root isInvalid={isInvalid}>
|
|
<Input
|
|
label={label}
|
|
placeholder={placeholder}
|
|
value={value}
|
|
onChangeText={onChangeText}
|
|
isInvalid={isInvalid}
|
|
{...rest}
|
|
/>
|
|
</Root>
|
|
)
|
|
}
|
|
|
|
const refs = mergeRefs([ctx.inputRef, inputRef!].filter(Boolean))
|
|
|
|
return (
|
|
<>
|
|
<Component
|
|
accessibilityHint={undefined}
|
|
{...rest}
|
|
accessibilityLabel={label}
|
|
ref={refs}
|
|
value={value}
|
|
onChangeText={onChangeText}
|
|
onFocus={e => {
|
|
ctx.onFocus()
|
|
onFocus?.(e)
|
|
}}
|
|
onBlur={e => {
|
|
ctx.onBlur()
|
|
onBlur?.(e)
|
|
}}
|
|
placeholder={placeholder || label}
|
|
placeholderTextColor={t.palette.contrast_500}
|
|
keyboardAppearance={t.name === 'light' ? 'light' : 'dark'}
|
|
hitSlop={HITSLOP_20}
|
|
style={[
|
|
a.relative,
|
|
a.z_20,
|
|
a.flex_1,
|
|
a.text_md,
|
|
t.atoms.text,
|
|
a.px_xs,
|
|
{
|
|
// paddingVertical doesn't work w/multiline - esb
|
|
paddingTop: 12,
|
|
paddingBottom: 13,
|
|
lineHeight: a.text_md.fontSize * 1.1875,
|
|
textAlignVertical: rest.multiline ? 'top' : undefined,
|
|
minHeight: rest.multiline ? 80 : undefined,
|
|
minWidth: 0,
|
|
},
|
|
// fix for autofill styles covering border
|
|
web({
|
|
paddingTop: 10,
|
|
paddingBottom: 11,
|
|
marginTop: 2,
|
|
marginBottom: 2,
|
|
}),
|
|
android({
|
|
paddingTop: 8,
|
|
paddingBottom: 8,
|
|
}),
|
|
style,
|
|
]}
|
|
/>
|
|
|
|
<View
|
|
style={[
|
|
a.z_10,
|
|
a.absolute,
|
|
a.inset_0,
|
|
a.rounded_sm,
|
|
t.atoms.bg_contrast_25,
|
|
{borderColor: 'transparent', borderWidth: 2},
|
|
ctx.hovered ? chromeHover : {},
|
|
ctx.focused ? chromeFocus : {},
|
|
ctx.isInvalid || isInvalid ? chromeError : {},
|
|
(ctx.isInvalid || isInvalid) && (ctx.hovered || ctx.focused)
|
|
? chromeErrorHover
|
|
: {},
|
|
]}
|
|
/>
|
|
</>
|
|
)
|
|
}
|
|
}
|
|
|
|
export const Input = createInput(TextInput)
|
|
|
|
export function LabelText({
|
|
nativeID,
|
|
children,
|
|
}: React.PropsWithChildren<{nativeID?: string}>) {
|
|
const t = useTheme()
|
|
return (
|
|
<Text
|
|
nativeID={nativeID}
|
|
style={[a.text_sm, a.font_bold, t.atoms.text_contrast_medium, a.mb_sm]}>
|
|
{children}
|
|
</Text>
|
|
)
|
|
}
|
|
|
|
export function Icon({icon: Comp}: {icon: React.ComponentType<SVGIconProps>}) {
|
|
const t = useTheme()
|
|
const ctx = React.useContext(Context)
|
|
const {hover, focus, errorHover, errorFocus} = React.useMemo(() => {
|
|
const hover: TextStyle[] = [
|
|
{
|
|
color: t.palette.contrast_800,
|
|
},
|
|
]
|
|
const focus: TextStyle[] = [
|
|
{
|
|
color: t.palette.primary_500,
|
|
},
|
|
]
|
|
const errorHover: TextStyle[] = [
|
|
{
|
|
color: t.palette.negative_500,
|
|
},
|
|
]
|
|
const errorFocus: TextStyle[] = [
|
|
{
|
|
color: t.palette.negative_500,
|
|
},
|
|
]
|
|
|
|
return {
|
|
hover,
|
|
focus,
|
|
errorHover,
|
|
errorFocus,
|
|
}
|
|
}, [t])
|
|
|
|
return (
|
|
<View style={[a.z_20, a.pr_xs]}>
|
|
<Comp
|
|
size="md"
|
|
style={[
|
|
{color: t.palette.contrast_500, pointerEvents: 'none', flexShrink: 0},
|
|
ctx.hovered ? hover : {},
|
|
ctx.focused ? focus : {},
|
|
ctx.isInvalid && ctx.hovered ? errorHover : {},
|
|
ctx.isInvalid && ctx.focused ? errorFocus : {},
|
|
]}
|
|
/>
|
|
</View>
|
|
)
|
|
}
|
|
|
|
export function SuffixText({
|
|
children,
|
|
label,
|
|
accessibilityHint,
|
|
style,
|
|
}: React.PropsWithChildren<
|
|
TextStyleProp & {
|
|
label: string
|
|
accessibilityHint?: AccessibilityProps['accessibilityHint']
|
|
}
|
|
>) {
|
|
const t = useTheme()
|
|
const ctx = React.useContext(Context)
|
|
return (
|
|
<Text
|
|
accessibilityLabel={label}
|
|
accessibilityHint={accessibilityHint}
|
|
numberOfLines={1}
|
|
style={[
|
|
a.z_20,
|
|
a.pr_sm,
|
|
a.text_md,
|
|
t.atoms.text_contrast_medium,
|
|
{
|
|
pointerEvents: 'none',
|
|
},
|
|
web({
|
|
marginTop: -2,
|
|
}),
|
|
ctx.hovered || ctx.focused
|
|
? {
|
|
color: t.palette.contrast_800,
|
|
}
|
|
: {},
|
|
style,
|
|
]}>
|
|
{children}
|
|
</Text>
|
|
)
|
|
}
|