This commit is contained in:
Hailey
2025-05-06 10:56:06 -07:00
26 changed files with 1179 additions and 451 deletions
@@ -30,7 +30,10 @@ const NativeView: React.ComponentType<
const NativeModule = requireNativeModule('BottomSheet') const NativeModule = requireNativeModule('BottomSheet')
const isIOS15 = Platform.OS === 'ios' && Number(Platform.Version) < 16 const isIOS15 =
Platform.OS === 'ios' &&
// semvar - can be 3 segments, so can't use Number(Platform.Version)
Number(Platform.Version.split('.').at(0)) < 16
export class BottomSheetNativeComponent extends React.Component< export class BottomSheetNativeComponent extends React.Component<
BottomSheetViewProps, BottomSheetViewProps,
@@ -9,7 +9,7 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
} }
} }
private var scrollView: UIScrollView? private var rctScrollView: RCTScrollView?
private var rctRefreshCtrl: RCTRefreshControl? private var rctRefreshCtrl: RCTRefreshControl?
private var cancelGestureRecognizers: [UIGestureRecognizer]? private var cancelGestureRecognizers: [UIGestureRecognizer]?
private var animTimer: Timer? private var animTimer: Timer?
@@ -68,7 +68,7 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
} }
@IBAction func callOnPan(_ sender: UIPanGestureRecognizer) { @IBAction func callOnPan(_ sender: UIPanGestureRecognizer) {
guard let sv = self.scrollView else { guard let rctsv = self.rctScrollView, let sv = rctsv.scrollView else {
return return
} }
@@ -113,7 +113,7 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
} }
func startDecayAnimation(_ translation: CGFloat, _ velocity: CGFloat) { func startDecayAnimation(_ translation: CGFloat, _ velocity: CGFloat) {
guard let sv = self.scrollView else { guard let sv = self.rctScrollView?.scrollView else {
return return
} }
@@ -161,48 +161,31 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
return offset return offset
} }
private func findScrollView(in view: UIView, foundCount: Int) -> UIScrollView? {
var foundCount = foundCount
if let sv = view as? UIScrollView { return sv }
for child in view.subviews {
if let found = findScrollView(in: child, foundCount: foundCount) {
if foundCount == 1 {
print("found sv: \(found)")
// return found
} else {
print("found sv: \(found)")
foundCount += 1
}
}
}
return nil
}
func tryFindScrollView() { func tryFindScrollView() {
guard let scrollViewTag = scrollViewTag else {
return
}
// Before we switch to a different scrollview, we always want to remove the cancel gesture recognizer. // Before we switch to a different scrollview, we always want to remove the cancel gesture recognizer.
// Otherwise we might end up with duplicates when we switch back to that scrollview. // Otherwise we might end up with duplicates when we switch back to that scrollview.
self.removeCancelGestureRecognizers() self.removeCancelGestureRecognizers()
guard let sv = self.findScrollView(in: self.superview!.superview!.superview!, foundCount: 0) else { self.rctScrollView = self.appContext?
print("⚠️ ExpoScrollForwarder: couldnt find UIScrollView under tag \(tag)") .findView(withTag: scrollViewTag, ofType: RCTScrollView.self)
return self.rctRefreshCtrl = self.rctScrollView?.scrollView.refreshControl as? RCTRefreshControl
}
self.scrollView = sv
self.rctRefreshCtrl = sv.refreshControl as? RCTRefreshControl
self.addCancelGestureRecognizers() self.addCancelGestureRecognizers()
} }
func addCancelGestureRecognizers() { func addCancelGestureRecognizers() {
self.cancelGestureRecognizers?.forEach { r in self.cancelGestureRecognizers?.forEach { r in
self.scrollView?.addGestureRecognizer(r) self.rctScrollView?.scrollView?.addGestureRecognizer(r)
} }
} }
func removeCancelGestureRecognizers() { func removeCancelGestureRecognizers() {
self.cancelGestureRecognizers?.forEach { r in self.cancelGestureRecognizers?.forEach { r in
self.scrollView?.removeGestureRecognizer(r) self.rctScrollView?.scrollView?.removeGestureRecognizer(r)
} }
} }
@@ -219,7 +202,7 @@ class ExpoScrollForwarderView: ExpoView, UIGestureRecognizerDelegate {
} }
func scrollToOffset(_ offset: Int, animated: Bool = true) { func scrollToOffset(_ offset: Int, animated: Bool = true) {
self.scrollView?.scrollRectToVisible(CGRect(x: 0, y: offset, width: 0, height: 0), animated: animated) self.rctScrollView?.scroll(toOffset: CGPoint(x: 0, y: offset), animated: animated)
} }
func stopTimer() { func stopTimer() {
-1
View File
@@ -193,7 +193,6 @@
"react-native-keyboard-controller": "^1.17.1", "react-native-keyboard-controller": "^1.17.1",
"react-native-mmkv": "^2.12.2", "react-native-mmkv": "^2.12.2",
"react-native-pager-view": "6.7.1", "react-native-pager-view": "6.7.1",
"react-native-picker-select": "^9.3.1",
"react-native-progress": "bluesky-social/react-native-progress", "react-native-progress": "bluesky-social/react-native-progress",
"react-native-qrcode-styled": "^0.3.3", "react-native-qrcode-styled": "^0.3.3",
"react-native-reanimated": "~3.17.5", "react-native-reanimated": "~3.17.5",
+3
View File
@@ -204,6 +204,9 @@ export const atoms = {
flex_grow: { flex_grow: {
flexGrow: 1, flexGrow: 1,
}, },
flex_grow_0: {
flexGrow: 0,
},
flex_shrink: { flex_shrink: {
flexShrink: 1, flexShrink: 1,
}, },
+47 -40
View File
@@ -1,17 +1,19 @@
import React from 'react' import React from 'react'
import {View} from 'react-native' import {msg} from '@lingui/macro'
import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select' import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {sanitizeAppLanguageSetting} from '#/locale/helpers' import {sanitizeAppLanguageSetting} from '#/locale/helpers'
import {APP_LANGUAGES} from '#/locale/languages' import {APP_LANGUAGES} from '#/locale/languages'
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
import {resetPostsFeedQueries} from '#/state/queries/post-feed' import {resetPostsFeedQueries} from '#/state/queries/post-feed'
import {atoms as a, useTheme, ViewStyleProp} from '#/alf' import {atoms as a, platform, useTheme} from '#/alf'
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron' import * as Select from '#/components/Select'
import {Button} from './Button'
export function AppLanguageDropdown(_props: ViewStyleProp) { export function AppLanguageDropdown() {
const t = useTheme() const t = useTheme()
const {_} = useLingui()
const queryClient = useQueryClient() const queryClient = useQueryClient()
const langPrefs = useLanguagePrefs() const langPrefs = useLanguagePrefs()
@@ -19,7 +21,7 @@ export function AppLanguageDropdown(_props: ViewStyleProp) {
const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage) const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage)
const onChangeAppLanguage = React.useCallback( const onChangeAppLanguage = React.useCallback(
(value: Parameters<PickerSelectProps['onValueChange']>[0]) => { (value: string) => {
if (!value) return if (!value) return
if (sanitizedLang !== value) { if (sanitizedLang !== value) {
setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value))
@@ -32,43 +34,48 @@ export function AppLanguageDropdown(_props: ViewStyleProp) {
) )
return ( return (
<View style={a.relative}> <Select.Root
<RNPickerSelect value={sanitizeAppLanguageSetting(langPrefs.appLanguage)}
darkTheme={t.scheme === 'dark'} onValueChange={onChangeAppLanguage}>
placeholder={{}} <Select.Trigger label={_(msg`Change app language`)}>
value={sanitizedLang} {({props}) => (
onValueChange={onChangeAppLanguage} <Button
items={APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({ {...props}
label={props.accessibilityLabel}
size={platform({
web: 'tiny',
native: 'small',
})}
variant="ghost"
color="secondary"
style={[
a.pr_xs,
a.pl_sm,
platform({
web: [{alignSelf: 'flex-start'}, a.gap_sm],
native: [a.gap_xs],
}),
]}>
<Select.ValueText
placeholder={_(msg`Select an app language`)}
style={[t.atoms.text_contrast_medium]}
/>
<Select.Icon style={[t.atoms.text_contrast_medium]} />
</Button>
)}
</Select.Trigger>
<Select.Content
renderItem={({label, value}) => (
<Select.Item value={value} label={label}>
<Select.ItemIndicator />
<Select.ItemText>{label}</Select.ItemText>
</Select.Item>
)}
items={APP_LANGUAGES.map(l => ({
label: l.name, label: l.name,
value: l.code2, value: l.code2,
key: l.code2,
}))} }))}
useNativeAndroidPickerStyle={false}
style={{
inputAndroid: {
color: t.atoms.text_contrast_medium.color,
fontSize: 16,
paddingRight: 12 + 4,
},
inputIOS: {
color: t.atoms.text.color,
fontSize: 16,
paddingRight: 12 + 4,
},
}}
/> />
</Select.Root>
<View
style={[
a.absolute,
a.inset_0,
{left: 'auto'},
{pointerEvents: 'none'},
a.align_center,
a.justify_center,
]}>
<ChevronDown fill={t.atoms.text.color} size="xs" />
</View>
</View>
) )
} }
@@ -1,83 +0,0 @@
import React from 'react'
import {View} from 'react-native'
import {useQueryClient} from '@tanstack/react-query'
import {sanitizeAppLanguageSetting} from '#/locale/helpers'
import {APP_LANGUAGES} from '#/locale/languages'
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
import {resetPostsFeedQueries} from '#/state/queries/post-feed'
import {atoms as a, useTheme, ViewStyleProp} from '#/alf'
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDown} from '#/components/icons/Chevron'
import {Text} from '#/components/Typography'
export function AppLanguageDropdown({style}: ViewStyleProp) {
const t = useTheme()
const queryClient = useQueryClient()
const langPrefs = useLanguagePrefs()
const setLangPrefs = useLanguagePrefsApi()
const sanitizedLang = sanitizeAppLanguageSetting(langPrefs.appLanguage)
const onChangeAppLanguage = React.useCallback(
(ev: React.ChangeEvent<HTMLSelectElement>) => {
const value = ev.target.value
if (!value) return
if (sanitizedLang !== value) {
setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value))
}
// reset feeds to refetch content
resetPostsFeedQueries(queryClient)
},
[sanitizedLang, setLangPrefs, queryClient],
)
return (
<View
style={[
// We don't have hitSlop here to increase the tap region,
// alternative is negative margins.
{height: 32, marginVertical: -((32 - 14) / 2)},
style,
]}>
<View
style={[
a.flex_row,
a.gap_sm,
a.align_center,
a.flex_shrink,
a.h_full,
t.atoms.bg,
]}>
<Text aria-hidden={true} style={t.atoms.text_contrast_medium}>
{APP_LANGUAGES.find(l => l.code2 === sanitizedLang)?.name}
</Text>
<ChevronDown fill={t.atoms.text.color} size="xs" style={a.flex_0} />
</View>
<select
value={sanitizedLang}
onChange={onChangeAppLanguage}
style={{
fontSize: a.text_sm.fontSize,
letterSpacing: a.text_sm.letterSpacing,
cursor: 'pointer',
position: 'absolute',
inset: 0,
opacity: 0,
color: t.atoms.text.color,
background: t.atoms.bg.backgroundColor,
padding: 4,
maxWidth: '100%',
}}>
{APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => (
<option key={l.code2} value={l.code2}>
{l.name}
</option>
))}
</select>
</View>
)
}
+11 -2
View File
@@ -1,5 +1,11 @@
import React from 'react' import {
import {StyleProp, TextStyle, View, ViewStyle} from 'react-native' type LayoutChangeEvent,
type StyleProp,
type TextStyle,
View,
type ViewStyle,
} from 'react-native'
import type React from 'react'
import {atoms as a, useTheme} from '#/alf' import {atoms as a, useTheme} from '#/alf'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
@@ -9,15 +15,18 @@ export function Header({
renderRight, renderRight,
children, children,
style, style,
onLayout,
}: { }: {
renderLeft?: () => React.ReactNode renderLeft?: () => React.ReactNode
renderRight?: () => React.ReactNode renderRight?: () => React.ReactNode
children?: React.ReactNode children?: React.ReactNode
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
onLayout?: (event: LayoutChangeEvent) => void
}) { }) {
const t = useTheme() const t = useTheme()
return ( return (
<View <View
onLayout={onLayout}
style={[ style={[
a.relative, a.relative,
a.w_full, a.w_full,
+289
View File
@@ -0,0 +1,289 @@
import {
createContext,
useCallback,
useContext,
useLayoutEffect,
useMemo,
useState,
} from 'react'
import {View} from 'react-native'
import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useTheme} from '#/alf'
import {atoms as a} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
import {ChevronTopBottom_Stroke2_Corner0_Rounded as ChevronUpDownIcon} from '#/components/icons/Chevron'
import {Text} from '#/components/Typography'
import {
type ContentProps,
type IconProps,
type ItemIndicatorProps,
type ItemProps,
type ItemTextProps,
type RootProps,
type TriggerProps,
type ValueProps,
} from './types'
type ContextType = {
control: Dialog.DialogControlProps
} & Pick<RootProps, 'value' | 'onValueChange' | 'disabled'>
const Context = createContext<ContextType | null>(null)
const ValueTextContext = createContext<
[any, React.Dispatch<React.SetStateAction<any>>]
>([undefined, () => {}])
function useSelectContext() {
const ctx = useContext(Context)
if (!ctx) {
throw new Error('Select components must must be used within a Select.Root')
}
return ctx
}
export function Root({children, value, onValueChange, disabled}: RootProps) {
const control = Dialog.useDialogControl()
const valueTextCtx = useState<any>()
const ctx = useMemo(
() => ({
control,
value,
onValueChange,
disabled,
}),
[control, value, onValueChange, disabled],
)
return (
<Context.Provider value={ctx}>
<ValueTextContext.Provider value={valueTextCtx}>
{children}
</ValueTextContext.Provider>
</Context.Provider>
)
}
export function Trigger({children, label}: TriggerProps) {
const {control} = useSelectContext()
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
const {
state: pressed,
onIn: onPressIn,
onOut: onPressOut,
} = useInteractionState()
if (typeof children === 'function') {
return children({
isNative: true,
control,
state: {
hovered: false,
focused,
pressed,
},
props: {
onPress: control.open,
onFocus,
onBlur,
onPressIn,
onPressOut,
accessibilityLabel: label,
},
})
} else {
return (
<Button
label={label}
onPress={control.open}
style={[a.flex_1, a.justify_between]}
color="secondary"
size="small"
variant="solid">
<>{children}</>
</Button>
)
}
}
export function ValueText({
placeholder,
children = value => value.label,
style,
}: ValueProps) {
const [value] = useContext(ValueTextContext)
const t = useTheme()
let text = value && children(value)
if (typeof text !== 'string') text = placeholder
return (
<ButtonText style={[t.atoms.text, a.font_normal, style]}>{text}</ButtonText>
)
}
export function Icon({}: IconProps) {
return <ButtonIcon icon={ChevronUpDownIcon} />
}
export function Content<T>({
items,
valueExtractor = defaultItemValueExtractor,
...props
}: ContentProps<T>) {
const {control, ...context} = useSelectContext()
const [, setValue] = useContext(ValueTextContext)
useLayoutEffect(() => {
const item = items.find(item => valueExtractor(item) === context.value)
if (item) {
setValue(item)
}
}, [items, context.value, valueExtractor, setValue])
return (
<Dialog.Outer control={control}>
<ContentInner
control={control}
items={items}
valueExtractor={valueExtractor}
{...props}
{...context}
/>
</Dialog.Outer>
)
}
function ContentInner<T>({
items,
renderItem,
valueExtractor,
...context
}: ContentProps<T> & ContextType) {
const control = Dialog.useDialogContext()
const {_} = useLingui()
const [headerHeight, setHeaderHeight] = useState(50)
const render = useCallback(
({item, index}: {item: T; index: number}) => {
return renderItem(item, index, context.value)
},
[renderItem, context.value],
)
const doneButton = useCallback(
() => (
<Button
label={_(msg`Done`)}
onPress={() => control.close()}
size="small"
color="primary"
variant="ghost"
style={[a.rounded_full]}>
<ButtonText style={[a.text_md]}>
<Trans>Done</Trans>
</ButtonText>
</Button>
),
[control, _],
)
return (
<Context.Provider value={context}>
<Dialog.Header
renderRight={doneButton}
onLayout={evt => setHeaderHeight(evt.nativeEvent.layout.height)}
style={[a.absolute, a.top_0, a.left_0, a.right_0, a.z_10]}>
<Dialog.HeaderText>
<Trans>Select an option</Trans>
</Dialog.HeaderText>
</Dialog.Header>
<Dialog.InnerFlatList
headerOffset={headerHeight}
data={items}
renderItem={render}
keyExtractor={valueExtractor}
/>
</Context.Provider>
)
}
function defaultItemValueExtractor(item: any) {
return item.value
}
const ItemContext = createContext<{
selected: boolean
hovered: boolean
focused: boolean
pressed: boolean
}>({
selected: false,
hovered: false,
focused: false,
pressed: false,
})
export function useItemContext() {
return useContext(ItemContext)
}
export function Item({children, value, label, style}: ItemProps) {
const t = useTheme()
const control = Dialog.useDialogContext()
const {value: selected, onValueChange} = useSelectContext()
return (
<Button
role="listitem"
label={label}
style={[a.flex_1]}
onPress={() => {
control.close(() => {
onValueChange?.(value)
})
}}>
{({hovered, focused, pressed}) => (
<ItemContext.Provider
value={{selected: value === selected, hovered, focused, pressed}}>
<View
style={[
a.flex_1,
a.pl_md,
(focused || pressed) && t.atoms.bg_contrast_25,
a.flex_row,
a.align_center,
a.gap_sm,
style,
]}>
{children}
</View>
</ItemContext.Provider>
)}
</Button>
)
}
export function ItemText({children}: ItemTextProps) {
const {selected} = useItemContext()
const t = useTheme()
// eslint-disable-next-line bsky-internal/avoid-unwrapped-text
return (
<View style={[a.flex_1, a.py_md, a.border_b, t.atoms.border_contrast_low]}>
<Text style={[a.text_md, selected && a.font_bold]}>{children}</Text>
</View>
)
}
export function ItemIndicator({icon: Icon = CheckIcon}: ItemIndicatorProps) {
const {selected} = useItemContext()
return <View style={{width: 24}}>{selected && <Icon size="md" />}</View>
}
+280
View File
@@ -0,0 +1,280 @@
import {createContext, forwardRef, useContext, useMemo} from 'react'
import {View} from 'react-native'
import {Select as RadixSelect} from 'radix-ui'
import {flatten, useTheme} from '#/alf'
import {atoms as a} from '#/alf'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
import {
ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon,
ChevronTop_Stroke2_Corner0_Rounded as ChevronUpIcon,
} from '#/components/icons/Chevron'
import {Text} from '#/components/Typography'
import {
type ContentProps,
type IconProps,
type ItemIndicatorProps,
type ItemProps,
type RadixPassThroughTriggerProps,
type RootProps,
type TriggerProps,
type ValueProps,
} from './types'
const SelectedValueContext = createContext<string | undefined | null>(null)
export function Root(props: RootProps) {
return (
<SelectedValueContext.Provider value={props.value}>
<RadixSelect.Root {...props} />
</SelectedValueContext.Provider>
)
}
const RadixTriggerPassThrough = forwardRef(
(
props: {
children: (
props: RadixPassThroughTriggerProps & {
ref: React.Ref<any>
},
) => React.ReactNode
},
ref,
) => {
// @ts-expect-error Radix provides no types of this stuff
return props.children?.({...props, ref})
},
)
RadixTriggerPassThrough.displayName = 'RadixTriggerPassThrough'
export function Trigger({children, label}: TriggerProps) {
const t = useTheme()
const {
state: hovered,
onIn: onMouseEnter,
onOut: onMouseLeave,
} = useInteractionState()
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
if (typeof children === 'function') {
return (
<RadixSelect.Trigger asChild>
<RadixTriggerPassThrough>
{props =>
children({
isNative: false,
state: {
hovered,
focused,
pressed: false,
},
props: {
...props,
onFocus: onFocus,
onBlur: onBlur,
onMouseEnter,
onMouseLeave,
accessibilityLabel: label,
},
})
}
</RadixTriggerPassThrough>
</RadixSelect.Trigger>
)
} else {
return (
<RadixSelect.Trigger
onFocus={onFocus}
onBlur={onBlur}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
style={flatten([
a.flex,
a.relative,
t.atoms.bg_contrast_25,
a.rounded_sm,
a.w_full,
{maxWidth: 400},
a.align_center,
a.gap_sm,
a.justify_between,
a.py_sm,
a.px_md,
{
outline: 0,
borderWidth: 2,
borderStyle: 'solid',
borderColor: focused
? t.palette.primary_500
: hovered
? t.palette.contrast_100
: t.palette.contrast_25,
},
])}>
{children}
</RadixSelect.Trigger>
)
}
}
export function ValueText({children: _, style, ...props}: ValueProps) {
return (
<Text style={style}>
<RadixSelect.Value {...props} />
</Text>
)
}
export function Icon({style}: IconProps) {
const t = useTheme()
return (
<RadixSelect.Icon>
<ChevronDownIcon style={[t.atoms.text, style]} size="xs" />
</RadixSelect.Icon>
)
}
export function Content<T>({items, renderItem}: ContentProps<T>) {
const t = useTheme()
const selectedValue = useContext(SelectedValueContext)
const scrollBtnStyles: React.CSSProperties[] = [
a.absolute,
a.flex,
a.align_center,
a.justify_center,
a.rounded_sm,
a.z_10,
]
const up: React.CSSProperties[] = [
...scrollBtnStyles,
a.pt_sm,
a.pb_lg,
{
top: 0,
left: 0,
right: 0,
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
background: `linear-gradient(to bottom, ${t.atoms.bg.backgroundColor} 0%, transparent 100%)`,
},
]
const down: React.CSSProperties[] = [
...scrollBtnStyles,
a.pt_lg,
a.pb_sm,
{
bottom: 0,
left: 0,
right: 0,
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
background: `linear-gradient(to top, ${t.atoms.bg.backgroundColor} 0%, transparent 100%)`,
},
]
return (
<RadixSelect.Portal>
<RadixSelect.Content
style={flatten([t.atoms.bg, a.rounded_sm, a.overflow_hidden])}
position="popper"
sideOffset={5}
className="radix-select-content">
<View
style={[
a.flex_1,
a.border,
t.atoms.border_contrast_low,
a.rounded_sm,
]}>
<RadixSelect.ScrollUpButton style={flatten(up)}>
<ChevronUpIcon style={[t.atoms.text]} size="xs" />
</RadixSelect.ScrollUpButton>
<RadixSelect.Viewport style={flatten([a.p_xs])}>
{items.map((item, index) => renderItem(item, index, selectedValue))}
</RadixSelect.Viewport>
<RadixSelect.ScrollDownButton style={flatten(down)}>
<ChevronDownIcon style={[t.atoms.text]} size="xs" />
</RadixSelect.ScrollDownButton>
</View>
</RadixSelect.Content>
</RadixSelect.Portal>
)
}
const ItemContext = createContext<{
hovered: boolean
focused: boolean
pressed: boolean
selected: boolean
}>({
hovered: false,
focused: false,
pressed: false,
selected: false,
})
export function useItemContext() {
return useContext(ItemContext)
}
export function Item({ref, value, style, children}: ItemProps) {
const t = useTheme()
const {
state: hovered,
onIn: onMouseEnter,
onOut: onMouseLeave,
} = useInteractionState()
const selected = useContext(SelectedValueContext) === value
const {state: focused, onIn: onFocus, onOut: onBlur} = useInteractionState()
const ctx = useMemo(
() => ({hovered, focused, pressed: false, selected}),
[hovered, focused, selected],
)
return (
<RadixSelect.Item
ref={ref}
value={value}
onMouseEnter={onMouseEnter}
onMouseLeave={onMouseLeave}
onFocus={onFocus}
onBlur={onBlur}
style={flatten([
a.relative,
a.flex,
{minHeight: 25, paddingLeft: 30, paddingRight: 35},
a.user_select_none,
a.align_center,
a.rounded_xs,
a.py_2xs,
a.text_sm,
{outline: 0},
(hovered || focused) && {backgroundColor: t.palette.primary_50},
selected && [a.font_bold],
a.transition_color,
style,
])}>
<ItemContext.Provider value={ctx}>{children}</ItemContext.Provider>
</RadixSelect.Item>
)
}
export const ItemText = RadixSelect.ItemText
export function ItemIndicator({icon: Icon = CheckIcon}: ItemIndicatorProps) {
return (
<RadixSelect.ItemIndicator
style={flatten([
a.absolute,
{left: 0, width: 30},
a.flex,
a.align_center,
a.justify_center,
])}>
<Icon size="sm" />
</RadixSelect.ItemIndicator>
)
}
+185
View File
@@ -0,0 +1,185 @@
import {
type AccessibilityProps,
type StyleProp,
type TextStyle,
type ViewStyle,
} from 'react-native'
import {type TextStyleProp} from '#/alf'
import {type DialogControlProps} from '#/components/Dialog'
import {type Props as SVGIconProps} from '#/components/icons/common'
export type RootProps = {
children?: React.ReactNode
value?: string
onValueChange?: (value: string) => void
disabled?: boolean
/**
* @platform web
*/
defaultValue?: string
/**
* @platform web
*/
open?: boolean
/**
* @platform web
*/
defaultOpen?: boolean
/**
* @platform web
*/
onOpenChange?(open: boolean): void
/**
* @platform web
*/
name?: string
/**
* @platform web
*/
autoComplete?: string
/**
* @platform web
*/
required?: boolean
}
export type RadixPassThroughTriggerProps = {
id: string
type: 'button'
disabled: boolean
['data-disabled']: boolean
['data-state']: string
['aria-controls']?: string
['aria-haspopup']?: boolean
['aria-expanded']?: AccessibilityProps['aria-expanded']
onPress: () => void
}
export type TriggerProps = {
children: React.ReactNode | ((props: TriggerChildProps) => React.ReactNode)
label: string
}
export type TriggerChildProps =
| {
isNative: true
control: DialogControlProps
state: {
/**
* Web only, `false` on native
*/
hovered: false
focused: boolean
pressed: boolean
}
/**
* We don't necessarily know what these will be spread on to, so we
* should add props one-by-one.
*
* On web, these properties are applied to a parent `Pressable`, so this
* object is empty.
*/
props: {
onPress: () => void
onFocus: () => void
onBlur: () => void
onPressIn: () => void
onPressOut: () => void
accessibilityLabel: string
}
}
| {
isNative: false
state: {
hovered: boolean
focused: boolean
/**
* Native only, `false` on web
*/
pressed: false
}
props: RadixPassThroughTriggerProps & {
onPress: () => void
onFocus: () => void
onBlur: () => void
onMouseEnter: () => void
onMouseLeave: () => void
accessibilityLabel: string
}
}
/*
* For use within the `Select.Trigger` component.
* Shows the currently selected value. You can also
* provide a placeholder to show when no value is selected.
*
* If you're passing items of a different shape than {value: string, label: string},
* you'll need to pass a function to `children` that extracts the label from an item.
*/
export type ValueProps = {
/**
* Only needed for native. Extracts the label from an item. Defaults to `item => item.label`
*/
children?: (value: any) => string
placeholder?: string
style?: StyleProp<TextStyle>
}
/*
* Icon for use within the `Select.Trigger` component.
* Changes based on platform - chevron down on web, up/down chevrons on native
*
* `style` prop is web only
*/
export type IconProps = TextStyleProp
export type ContentProps<T> = {
/**
* Items to render. Recommended to be in the form {value: string, label: string} - if not,
* you need to provide a `valueExtractor` function to extract the value from an item and
* customise the `Select.ValueText` component.
*/
items: T[]
/**
* Renders an item. You should probably use the `Select.Item` component.
*
* @example
* ```tsx
* renderItem={({label, value}) => (
* <Select.Item value={value} label={label}>
* <Select.ItemIndicator />
* <Select.ItemText>{label}</Select.ItemText>
* </Select.Item>
* )}
* ```
*/
renderItem: (
item: T,
index: number,
selectedValue?: string | null,
) => React.ReactElement
/*
* Extracts the value from an item. Defaults to `item => item.value`
*/
valueExtractor?: (item: T) => string
}
/*
* An item within the select dropdown
*/
export type ItemProps = {
ref?: React.Ref<HTMLDivElement>
value: string
label: string
children: React.ReactNode
style?: StyleProp<ViewStyle>
}
export type ItemTextProps = {
children: React.ReactNode
}
export type ItemIndicatorProps = {
icon?: React.ComponentType<SVGIconProps>
}
+59 -59
View File
@@ -74,8 +74,8 @@ msgstr ""
msgid "{0, plural, one {# second} other {# seconds}}" msgid "{0, plural, one {# second} other {# seconds}}"
msgstr "" msgstr ""
#: src/view/shell/bottom-bar/BottomBar.tsx:209 #: src/view/shell/bottom-bar/BottomBar.tsx:213
#: src/view/shell/bottom-bar/BottomBar.tsx:241 #: src/view/shell/bottom-bar/BottomBar.tsx:245
#: src/view/shell/Drawer.tsx:470 #: src/view/shell/Drawer.tsx:470
msgid "{0, plural, one {# unread item} other {# unread items}}" msgid "{0, plural, one {# unread item} other {# unread items}}"
msgstr "" msgstr ""
@@ -446,7 +446,7 @@ msgid "A new form of verification"
msgstr "" msgstr ""
#: src/Navigation.tsx:401 #: src/Navigation.tsx:401
#: src/screens/Settings/AboutSettings.tsx:72 #: src/screens/Settings/AboutSettings.tsx:75
#: src/screens/Settings/Settings.tsx:224 #: src/screens/Settings/Settings.tsx:224
#: src/screens/Settings/Settings.tsx:227 #: src/screens/Settings/Settings.tsx:227
msgid "About" msgid "About"
@@ -621,7 +621,7 @@ msgstr ""
msgid "Add muted words and tags" msgid "Add muted words and tags"
msgstr "" msgstr ""
#: src/view/com/composer/Composer.tsx:1306 #: src/view/com/composer/Composer.tsx:1308
msgid "Add new post" msgid "Add new post"
msgstr "" msgstr ""
@@ -822,7 +822,7 @@ msgstr ""
msgid "An error occurred while fetching the feed." msgid "An error occurred while fetching the feed."
msgstr "" msgstr ""
#: src/components/StarterPack/ProfileStarterPacks.tsx:336 #: src/components/StarterPack/ProfileStarterPacks.tsx:337
msgid "An error occurred while generating your starter pack. Want to try again?" msgid "An error occurred while generating your starter pack. Want to try again?"
msgstr "" msgstr ""
@@ -1119,7 +1119,7 @@ msgstr ""
msgid "Before creating a post, you must first verify your email." msgid "Before creating a post, you must first verify your email."
msgstr "" msgstr ""
#: src/components/StarterPack/ProfileStarterPacks.tsx:343 #: src/components/StarterPack/ProfileStarterPacks.tsx:344
msgid "Before creating a starter pack, you must first verify your email." msgid "Before creating a starter pack, you must first verify your email."
msgstr "" msgstr ""
@@ -1262,7 +1262,7 @@ msgstr ""
msgid "Bluesky Social Terms of Service" msgid "Bluesky Social Terms of Service"
msgstr "" msgstr ""
#: src/components/StarterPack/ProfileStarterPacks.tsx:303 #: src/components/StarterPack/ProfileStarterPacks.tsx:304
msgid "Bluesky will choose a set of recommended accounts from people in your network." msgid "Bluesky will choose a set of recommended accounts from people in your network."
msgstr "" msgstr ""
@@ -1519,7 +1519,7 @@ msgid "Changes hosting provider"
msgstr "" msgstr ""
#: src/Navigation.tsx:426 #: src/Navigation.tsx:426
#: src/view/shell/bottom-bar/BottomBar.tsx:205 #: src/view/shell/bottom-bar/BottomBar.tsx:209
#: src/view/shell/desktop/LeftNav.tsx:535 #: src/view/shell/desktop/LeftNav.tsx:535
#: src/view/shell/Drawer.tsx:438 #: src/view/shell/Drawer.tsx:438
msgid "Chat" msgid "Chat"
@@ -1587,7 +1587,7 @@ msgstr ""
msgid "Choose Feeds" msgid "Choose Feeds"
msgstr "" msgstr ""
#: src/components/StarterPack/ProfileStarterPacks.tsx:311 #: src/components/StarterPack/ProfileStarterPacks.tsx:312
msgid "Choose for me" msgid "Choose for me"
msgstr "" msgstr ""
@@ -1627,8 +1627,8 @@ msgstr ""
msgid "Clear all storage data (restart after this)" msgid "Clear all storage data (restart after this)"
msgstr "" msgstr ""
#: src/screens/Settings/AboutSettings.tsx:113 #: src/screens/Settings/AboutSettings.tsx:116
#: src/screens/Settings/AboutSettings.tsx:117 #: src/screens/Settings/AboutSettings.tsx:120
msgid "Clear image cache" msgid "Clear image cache"
msgstr "" msgstr ""
@@ -1807,7 +1807,7 @@ msgstr ""
msgid "Compose reply" msgid "Compose reply"
msgstr "" msgstr ""
#: src/view/com/composer/Composer.tsx:1688 #: src/view/com/composer/Composer.tsx:1690
msgid "Compressing video..." msgid "Compressing video..."
msgstr "" msgstr ""
@@ -1958,7 +1958,7 @@ msgstr ""
msgid "Copied" msgid "Copied"
msgstr "" msgstr ""
#: src/screens/Settings/AboutSettings.tsx:148 #: src/screens/Settings/AboutSettings.tsx:151
msgid "Copied build version to clipboard" msgid "Copied build version to clipboard"
msgstr "" msgstr ""
@@ -1976,7 +1976,7 @@ msgstr ""
msgid "Copied!" msgid "Copied!"
msgstr "" msgstr ""
#: src/screens/Settings/AboutSettings.tsx:124 #: src/screens/Settings/AboutSettings.tsx:127
msgid "Copies build version to clipboard" msgid "Copies build version to clipboard"
msgstr "" msgstr ""
@@ -2079,7 +2079,7 @@ msgstr ""
msgid "Could not process your video" msgid "Could not process your video"
msgstr "" msgstr ""
#: src/components/StarterPack/ProfileStarterPacks.tsx:293 #: src/components/StarterPack/ProfileStarterPacks.tsx:294
msgid "Create" msgid "Create"
msgstr "" msgstr ""
@@ -2087,20 +2087,20 @@ msgstr ""
msgid "Create a QR code for a starter pack" msgid "Create a QR code for a starter pack"
msgstr "" msgstr ""
#: src/components/StarterPack/ProfileStarterPacks.tsx:178 #: src/components/StarterPack/ProfileStarterPacks.tsx:179
#: src/components/StarterPack/ProfileStarterPacks.tsx:274 #: src/components/StarterPack/ProfileStarterPacks.tsx:275
#: src/Navigation.tsx:461 #: src/Navigation.tsx:461
msgid "Create a starter pack" msgid "Create a starter pack"
msgstr "" msgstr ""
#: src/components/StarterPack/ProfileStarterPacks.tsx:255 #: src/components/StarterPack/ProfileStarterPacks.tsx:256
msgid "Create a starter pack for me" msgid "Create a starter pack for me"
msgstr "" msgstr ""
#: src/view/com/auth/SplashScreen.tsx:55 #: src/view/com/auth/SplashScreen.tsx:55
#: src/view/com/auth/SplashScreen.web.tsx:117 #: src/view/com/auth/SplashScreen.web.tsx:117
#: src/view/shell/bottom-bar/BottomBar.tsx:315 #: src/view/shell/bottom-bar/BottomBar.tsx:319
#: src/view/shell/bottom-bar/BottomBar.tsx:320 #: src/view/shell/bottom-bar/BottomBar.tsx:324
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:199 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:199
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:204 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:204
#: src/view/shell/NavSignupCard.tsx:47 #: src/view/shell/NavSignupCard.tsx:47
@@ -2126,7 +2126,7 @@ msgstr ""
msgid "Create an avatar instead" msgid "Create an avatar instead"
msgstr "" msgstr ""
#: src/components/StarterPack/ProfileStarterPacks.tsx:185 #: src/components/StarterPack/ProfileStarterPacks.tsx:186
msgid "Create another" msgid "Create another"
msgstr "" msgstr ""
@@ -2350,12 +2350,12 @@ msgstr ""
msgid "Detach quote post?" msgid "Detach quote post?"
msgstr "" msgstr ""
#: src/screens/Settings/AboutSettings.tsx:137 #: src/screens/Settings/AboutSettings.tsx:140
msgctxt "toast" msgctxt "toast"
msgid "Developer mode disabled" msgid "Developer mode disabled"
msgstr "" msgstr ""
#: src/screens/Settings/AboutSettings.tsx:131 #: src/screens/Settings/AboutSettings.tsx:134
msgctxt "toast" msgctxt "toast"
msgid "Developer mode enabled" msgid "Developer mode enabled"
msgstr "" msgstr ""
@@ -2435,7 +2435,7 @@ msgstr ""
msgid "Dismiss" msgid "Dismiss"
msgstr "" msgstr ""
#: src/view/com/composer/Composer.tsx:1612 #: src/view/com/composer/Composer.tsx:1614
msgid "Dismiss error" msgid "Dismiss error"
msgstr "" msgstr ""
@@ -2886,7 +2886,7 @@ msgstr ""
msgid "Entertainment" msgid "Entertainment"
msgstr "" msgstr ""
#: src/view/com/composer/Composer.tsx:1697 #: src/view/com/composer/Composer.tsx:1699
#: src/view/com/util/error/ErrorScreen.tsx:42 #: src/view/com/util/error/ErrorScreen.tsx:42
msgid "Error" msgid "Error"
msgstr "" msgstr ""
@@ -3494,7 +3494,7 @@ msgstr ""
msgid "Gallery" msgid "Gallery"
msgstr "" msgstr ""
#: src/components/StarterPack/ProfileStarterPacks.tsx:300 #: src/components/StarterPack/ProfileStarterPacks.tsx:301
msgid "Generate a starter pack" msgid "Generate a starter pack"
msgstr "" msgstr ""
@@ -3780,7 +3780,7 @@ msgstr ""
#: src/Navigation.tsx:645 #: src/Navigation.tsx:645
#: src/Navigation.tsx:665 #: src/Navigation.tsx:665
#: src/view/shell/bottom-bar/BottomBar.tsx:162 #: src/view/shell/bottom-bar/BottomBar.tsx:166
#: src/view/shell/desktop/LeftNav.tsx:599 #: src/view/shell/desktop/LeftNav.tsx:599
#: src/view/shell/Drawer.tsx:412 #: src/view/shell/Drawer.tsx:412
msgid "Home" msgid "Home"
@@ -3871,12 +3871,12 @@ msgstr ""
msgid "Image" msgid "Image"
msgstr "" msgstr ""
#: src/screens/Settings/AboutSettings.tsx:61 #: src/screens/Settings/AboutSettings.tsx:64
msgid "Image cache cleared" msgid "Image cache cleared"
msgstr "" msgstr ""
#. Android-only toast message which includes amount of space freed using localized number formatting #. Android-only toast message which includes amount of space freed using localized number formatting
#: src/screens/Settings/AboutSettings.tsx:47 #: src/screens/Settings/AboutSettings.tsx:50
msgid "Image cache cleared, freed {0}" msgid "Image cache cleared, freed {0}"
msgstr "" msgstr ""
@@ -3997,7 +3997,7 @@ msgstr ""
msgid "It's just you right now! Add more people to your starter pack by searching above." msgid "It's just you right now! Add more people to your starter pack by searching above."
msgstr "" msgstr ""
#: src/view/com/composer/Composer.tsx:1631 #: src/view/com/composer/Composer.tsx:1633
msgid "Job ID: {0}" msgid "Job ID: {0}"
msgstr "" msgstr ""
@@ -4158,7 +4158,7 @@ msgstr ""
msgid "left to go." msgid "left to go."
msgstr "" msgstr ""
#: src/components/StarterPack/ProfileStarterPacks.tsx:316 #: src/components/StarterPack/ProfileStarterPacks.tsx:317
msgid "Let me choose" msgid "Let me choose"
msgstr "" msgstr ""
@@ -4374,7 +4374,7 @@ msgstr ""
msgid "Looks like you're missing a following feed. <0>Click here to add one.</0>" msgid "Looks like you're missing a following feed. <0>Click here to add one.</0>"
msgstr "" msgstr ""
#: src/components/StarterPack/ProfileStarterPacks.tsx:269 #: src/components/StarterPack/ProfileStarterPacks.tsx:270
msgid "Make one for me" msgid "Make one for me"
msgstr "" msgstr ""
@@ -5039,7 +5039,7 @@ msgstr ""
#: src/Navigation.tsx:655 #: src/Navigation.tsx:655
#: src/view/screens/Notifications.tsx:128 #: src/view/screens/Notifications.tsx:128
#: src/view/shell/bottom-bar/BottomBar.tsx:236 #: src/view/shell/bottom-bar/BottomBar.tsx:240
#: src/view/shell/desktop/LeftNav.tsx:636 #: src/view/shell/desktop/LeftNav.tsx:636
#: src/view/shell/Drawer.tsx:465 #: src/view/shell/Drawer.tsx:465
msgid "Notifications" msgid "Notifications"
@@ -5141,8 +5141,8 @@ msgid "Oops, something went wrong!"
msgstr "" msgstr ""
#: src/components/Lists.tsx:173 #: src/components/Lists.tsx:173
#: src/components/StarterPack/ProfileStarterPacks.tsx:325 #: src/components/StarterPack/ProfileStarterPacks.tsx:326
#: src/components/StarterPack/ProfileStarterPacks.tsx:334 #: src/components/StarterPack/ProfileStarterPacks.tsx:335
#: src/screens/Settings/AppPasswords.tsx:59 #: src/screens/Settings/AppPasswords.tsx:59
#: src/screens/Settings/components/ChangeHandleDialog.tsx:106 #: src/screens/Settings/components/ChangeHandleDialog.tsx:106
#: src/screens/Settings/NotificationSettings.tsx:54 #: src/screens/Settings/NotificationSettings.tsx:54
@@ -5172,7 +5172,7 @@ msgid "Open drawer menu"
msgstr "" msgstr ""
#: src/screens/Messages/components/MessageInput.web.tsx:181 #: src/screens/Messages/components/MessageInput.web.tsx:181
#: src/view/com/composer/Composer.tsx:1292 #: src/view/com/composer/Composer.tsx:1293
msgid "Open emoji picker" msgid "Open emoji picker"
msgstr "" msgstr ""
@@ -5185,7 +5185,7 @@ msgstr ""
msgid "Open feed options menu" msgid "Open feed options menu"
msgstr "" msgstr ""
#: src/components/dms/EmojiPopup.android.tsx:28 #: src/components/dms/EmojiPopup.android.tsx:27
msgid "Open full emoji list" msgid "Open full emoji list"
msgstr "" msgstr ""
@@ -5266,7 +5266,7 @@ msgstr ""
msgid "Opens device photo gallery" msgid "Opens device photo gallery"
msgstr "" msgstr ""
#: src/view/com/composer/Composer.tsx:1293 #: src/view/com/composer/Composer.tsx:1294
msgid "Opens emoji picker" msgid "Opens emoji picker"
msgstr "" msgstr ""
@@ -5774,15 +5774,15 @@ msgid "Privacy and Security"
msgstr "" msgstr ""
#: src/Navigation.tsx:301 #: src/Navigation.tsx:301
#: src/screens/Settings/AboutSettings.tsx:89
#: src/screens/Settings/AboutSettings.tsx:92 #: src/screens/Settings/AboutSettings.tsx:92
#: src/screens/Settings/AboutSettings.tsx:95
#: src/view/screens/PrivacyPolicy.tsx:31 #: src/view/screens/PrivacyPolicy.tsx:31
#: src/view/shell/Drawer.tsx:650 #: src/view/shell/Drawer.tsx:650
#: src/view/shell/Drawer.tsx:651 #: src/view/shell/Drawer.tsx:651
msgid "Privacy Policy" msgid "Privacy Policy"
msgstr "" msgstr ""
#: src/view/com/composer/Composer.tsx:1694 #: src/view/com/composer/Composer.tsx:1696
msgid "Processing video..." msgid "Processing video..."
msgstr "" msgstr ""
@@ -5796,7 +5796,7 @@ msgstr ""
msgid "profile" msgid "profile"
msgstr "" msgstr ""
#: src/view/shell/bottom-bar/BottomBar.tsx:286 #: src/view/shell/bottom-bar/BottomBar.tsx:290
#: src/view/shell/desktop/LeftNav.tsx:691 #: src/view/shell/desktop/LeftNav.tsx:691
#: src/view/shell/Drawer.tsx:74 #: src/view/shell/Drawer.tsx:74
#: src/view/shell/Drawer.tsx:542 #: src/view/shell/Drawer.tsx:542
@@ -6412,7 +6412,7 @@ msgstr ""
#: src/components/Error.tsx:65 #: src/components/Error.tsx:65
#: src/components/Lists.tsx:110 #: src/components/Lists.tsx:110
#: src/components/moderation/ReportDialog/index.tsx:221 #: src/components/moderation/ReportDialog/index.tsx:221
#: src/components/StarterPack/ProfileStarterPacks.tsx:339 #: src/components/StarterPack/ProfileStarterPacks.tsx:340
#: src/screens/Login/LoginForm.tsx:323 #: src/screens/Login/LoginForm.tsx:323
#: src/screens/Login/LoginForm.tsx:330 #: src/screens/Login/LoginForm.tsx:330
#: src/screens/Messages/ChatList.tsx:253 #: src/screens/Messages/ChatList.tsx:253
@@ -6473,7 +6473,7 @@ msgstr ""
msgid "Save" msgid "Save"
msgstr "" msgstr ""
#: src/view/com/lightbox/ImageViewing/index.tsx:608 #: src/view/com/lightbox/ImageViewing/index.tsx:610
#: src/view/com/modals/CreateOrEditList.tsx:325 #: src/view/com/modals/CreateOrEditList.tsx:325
msgctxt "action" msgctxt "action"
msgid "Save" msgid "Save"
@@ -6556,7 +6556,7 @@ msgstr ""
#: src/components/forms/SearchInput.tsx:36 #: src/components/forms/SearchInput.tsx:36
#: src/screens/Search/Shell.tsx:307 #: src/screens/Search/Shell.tsx:307
#: src/screens/Search/Shell.tsx:464 #: src/screens/Search/Shell.tsx:464
#: src/view/shell/bottom-bar/BottomBar.tsx:182 #: src/view/shell/bottom-bar/BottomBar.tsx:186
msgid "Search" msgid "Search"
msgstr "" msgstr ""
@@ -6884,7 +6884,7 @@ msgstr ""
msgid "Share" msgid "Share"
msgstr "" msgstr ""
#: src/view/com/lightbox/ImageViewing/index.tsx:617 #: src/view/com/lightbox/ImageViewing/index.tsx:619
msgctxt "action" msgctxt "action"
msgid "Share" msgid "Share"
msgstr "" msgstr ""
@@ -7073,8 +7073,8 @@ msgstr ""
#: src/view/com/auth/SplashScreen.tsx:69 #: src/view/com/auth/SplashScreen.tsx:69
#: src/view/com/auth/SplashScreen.web.tsx:123 #: src/view/com/auth/SplashScreen.web.tsx:123
#: src/view/com/auth/SplashScreen.web.tsx:131 #: src/view/com/auth/SplashScreen.web.tsx:131
#: src/view/shell/bottom-bar/BottomBar.tsx:325 #: src/view/shell/bottom-bar/BottomBar.tsx:329
#: src/view/shell/bottom-bar/BottomBar.tsx:330 #: src/view/shell/bottom-bar/BottomBar.tsx:334
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:209 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:209
#: src/view/shell/bottom-bar/BottomBarWeb.tsx:214 #: src/view/shell/bottom-bar/BottomBarWeb.tsx:214
#: src/view/shell/NavSignupCard.tsx:57 #: src/view/shell/NavSignupCard.tsx:57
@@ -7217,7 +7217,7 @@ msgstr ""
msgid "Something wrong? Let us know." msgid "Something wrong? Let us know."
msgstr "" msgstr ""
#: src/App.native.tsx:122 #: src/App.native.tsx:121
#: src/App.web.tsx:98 #: src/App.web.tsx:98
msgid "Sorry! Your session expired. Please sign in again." msgid "Sorry! Your session expired. Please sign in again."
msgstr "" msgstr ""
@@ -7303,12 +7303,12 @@ msgstr ""
msgid "Starter Packs" msgid "Starter Packs"
msgstr "" msgstr ""
#: src/components/StarterPack/ProfileStarterPacks.tsx:247 #: src/components/StarterPack/ProfileStarterPacks.tsx:248
msgid "Starter packs let you easily share your favorite feeds and people with your friends." msgid "Starter packs let you easily share your favorite feeds and people with your friends."
msgstr "" msgstr ""
#: src/screens/Settings/AboutSettings.tsx:97
#: src/screens/Settings/AboutSettings.tsx:100 #: src/screens/Settings/AboutSettings.tsx:100
#: src/screens/Settings/AboutSettings.tsx:103
msgid "Status Page" msgid "Status Page"
msgstr "" msgstr ""
@@ -7430,8 +7430,8 @@ msgstr ""
msgid "System" msgid "System"
msgstr "" msgstr ""
#: src/screens/Settings/AboutSettings.tsx:104
#: src/screens/Settings/AboutSettings.tsx:107 #: src/screens/Settings/AboutSettings.tsx:107
#: src/screens/Settings/AboutSettings.tsx:110
#: src/screens/Settings/Settings.tsx:380 #: src/screens/Settings/Settings.tsx:380
msgid "System log" msgid "System log"
msgstr "" msgstr ""
@@ -7485,8 +7485,8 @@ msgid "Terms"
msgstr "" msgstr ""
#: src/Navigation.tsx:306 #: src/Navigation.tsx:306
#: src/screens/Settings/AboutSettings.tsx:81
#: src/screens/Settings/AboutSettings.tsx:84 #: src/screens/Settings/AboutSettings.tsx:84
#: src/screens/Settings/AboutSettings.tsx:87
#: src/view/screens/TermsOfService.tsx:31 #: src/view/screens/TermsOfService.tsx:31
#: src/view/shell/Drawer.tsx:643 #: src/view/shell/Drawer.tsx:643
#: src/view/shell/Drawer.tsx:645 #: src/view/shell/Drawer.tsx:645
@@ -8369,7 +8369,7 @@ msgstr ""
msgid "Uploading link thumbnail..." msgid "Uploading link thumbnail..."
msgstr "" msgstr ""
#: src/view/com/composer/Composer.tsx:1691 #: src/view/com/composer/Composer.tsx:1693
msgid "Uploading video..." msgid "Uploading video..."
msgstr "" msgstr ""
@@ -8555,8 +8555,8 @@ msgstr ""
msgid "Verify Your Email" msgid "Verify Your Email"
msgstr "" msgstr ""
#: src/screens/Settings/AboutSettings.tsx:123 #: src/screens/Settings/AboutSettings.tsx:126
#: src/screens/Settings/AboutSettings.tsx:152 #: src/screens/Settings/AboutSettings.tsx:155
msgid "Version {appVersion}" msgid "Version {appVersion}"
msgstr "" msgstr ""
@@ -8599,7 +8599,7 @@ msgstr ""
msgid "Video settings" msgid "Video settings"
msgstr "" msgstr ""
#: src/view/com/composer/Composer.tsx:1701 #: src/view/com/composer/Composer.tsx:1703
msgid "Video uploaded" msgid "Video uploaded"
msgstr "" msgstr ""
@@ -8655,7 +8655,7 @@ msgstr ""
msgid "View details for reporting a copyright violation" msgid "View details for reporting a copyright violation"
msgstr "" msgstr ""
#: src/view/com/posts/ViewFullThread.tsx:56 #: src/view/com/posts/ViewFullThread.tsx:59
msgid "View full thread" msgid "View full thread"
msgstr "" msgstr ""
@@ -9162,7 +9162,7 @@ msgstr ""
msgid "You have temporarily reached the limit for video uploads. Please try again later." msgid "You have temporarily reached the limit for video uploads. Please try again later."
msgstr "" msgstr ""
#: src/components/StarterPack/ProfileStarterPacks.tsx:244 #: src/components/StarterPack/ProfileStarterPacks.tsx:245
msgid "You haven't created a starter pack yet!" msgid "You haven't created a starter pack yet!"
msgstr "" msgstr ""
@@ -9199,7 +9199,7 @@ msgstr ""
msgid "You must be 13 years of age or older to create an account." msgid "You must be 13 years of age or older to create an account."
msgstr "" msgstr ""
#: src/components/StarterPack/ProfileStarterPacks.tsx:327 #: src/components/StarterPack/ProfileStarterPacks.tsx:328
msgid "You must be following at least seven other people to generate a starter pack." msgid "You must be following at least seven other people to generate a starter pack."
msgstr "" msgstr ""
+44 -141
View File
@@ -1,23 +1,30 @@
import {useCallback, useMemo} from 'react' import {useCallback, useMemo} from 'react'
import {View} from 'react-native' import {View} from 'react-native'
import RNPickerSelect, {PickerSelectProps} from 'react-native-picker-select'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {APP_LANGUAGES, LANGUAGES} from '#/lib/../locale/languages' import {APP_LANGUAGES, LANGUAGES} from '#/lib/../locale/languages'
import {CommonNavigatorParams, NativeStackScreenProps} from '#/lib/routes/types' import {
type CommonNavigatorParams,
type NativeStackScreenProps,
} from '#/lib/routes/types'
import {languageName, sanitizeAppLanguageSetting} from '#/locale/helpers' import {languageName, sanitizeAppLanguageSetting} from '#/locale/helpers'
import {useModalControls} from '#/state/modals' import {useModalControls} from '#/state/modals'
import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences' import {useLanguagePrefs, useLanguagePrefsApi} from '#/state/preferences'
import {atoms as a, useTheme, web} from '#/alf' import {atoms as a, useTheme, web} from '#/alf'
import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {Button, ButtonIcon, ButtonText} from '#/components/Button'
import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check'
import {ChevronBottom_Stroke2_Corner0_Rounded as ChevronDownIcon} from '#/components/icons/Chevron'
import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus'
import * as Layout from '#/components/Layout' import * as Layout from '#/components/Layout'
import * as Select from '#/components/Select'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import * as SettingsList from './components/SettingsList' import * as SettingsList from './components/SettingsList'
const DEDUPED_LANGUAGES = LANGUAGES.filter(
(lang, i, arr) =>
lang.code2 && arr.findIndex(l => l.code2 === lang.code2) === i,
)
type Props = NativeStackScreenProps<CommonNavigatorParams, 'LanguageSettings'> type Props = NativeStackScreenProps<CommonNavigatorParams, 'LanguageSettings'>
export function LanguageSettingsScreen({}: Props) { export function LanguageSettingsScreen({}: Props) {
const {_} = useLingui() const {_} = useLingui()
@@ -32,7 +39,7 @@ export function LanguageSettingsScreen({}: Props) {
}, [openModal]) }, [openModal])
const onChangePrimaryLanguage = useCallback( const onChangePrimaryLanguage = useCallback(
(value: Parameters<PickerSelectProps['onValueChange']>[0]) => { (value: string) => {
if (!value) return if (!value) return
if (langPrefs.primaryLanguage !== value) { if (langPrefs.primaryLanguage !== value) {
setLangPrefs.setPrimaryLanguage(value) setLangPrefs.setPrimaryLanguage(value)
@@ -42,7 +49,7 @@ export function LanguageSettingsScreen({}: Props) {
) )
const onChangeAppLanguage = useCallback( const onChangeAppLanguage = useCallback(
(value: Parameters<PickerSelectProps['onValueChange']>[0]) => { (value: string) => {
if (!value) return if (!value) return
if (langPrefs.appLanguage !== value) { if (langPrefs.appLanguage !== value) {
setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value)) setLangPrefs.setAppLanguage(sanitizeAppLanguageSetting(value))
@@ -85,79 +92,26 @@ export function LanguageSettingsScreen({}: Props) {
Select which language to use for the app's user interface. Select which language to use for the app's user interface.
</Trans> </Trans>
</Text> </Text>
<View style={[a.relative, web([a.w_full, {maxWidth: 400}])]}> <Select.Root
<RNPickerSelect value={sanitizeAppLanguageSetting(langPrefs.appLanguage)}
darkTheme={t.scheme === 'dark'} onValueChange={onChangeAppLanguage}>
placeholder={{}} <Select.Trigger label={_(msg`Select app language`)}>
value={sanitizeAppLanguageSetting(langPrefs.appLanguage)} <Select.ValueText />
onValueChange={onChangeAppLanguage} <Select.Icon />
items={APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({ </Select.Trigger>
<Select.Content
renderItem={({label, value}) => (
<Select.Item value={value} label={label}>
<Select.ItemIndicator />
<Select.ItemText>{label}</Select.ItemText>
</Select.Item>
)}
items={APP_LANGUAGES.map(l => ({
label: l.name, label: l.name,
value: l.code2, value: l.code2,
key: l.code2,
}))} }))}
style={{
inputAndroid: {
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
color: t.atoms.text.color,
fontSize: 14,
letterSpacing: 0.5,
fontWeight: a.font_bold.fontWeight,
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: a.rounded_xs.borderRadius,
},
inputIOS: {
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
color: t.atoms.text.color,
fontSize: 14,
letterSpacing: 0.5,
fontWeight: a.font_bold.fontWeight,
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: a.rounded_xs.borderRadius,
},
inputWeb: {
flex: 1,
width: '100%',
cursor: 'pointer',
// @ts-ignore web only
'-moz-appearance': 'none',
'-webkit-appearance': 'none',
appearance: 'none',
outline: 0,
borderWidth: 0,
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
color: t.atoms.text.color,
fontSize: 14,
fontFamily: 'inherit',
letterSpacing: 0.5,
fontWeight: a.font_bold.fontWeight,
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: a.rounded_xs.borderRadius,
},
}}
/> />
</Select.Root>
<View
style={[
a.absolute,
t.atoms.bg_contrast_25,
a.rounded_xs,
a.pointer_events_none,
a.align_center,
a.justify_center,
{
top: 1,
right: 1,
bottom: 1,
width: 40,
},
]}>
<ChevronDownIcon style={[t.atoms.text]} />
</View>
</View>
</View> </View>
</SettingsList.Group> </SettingsList.Group>
<SettingsList.Divider /> <SettingsList.Divider />
@@ -171,77 +125,26 @@ export function LanguageSettingsScreen({}: Props) {
Select your preferred language for translations in your feed. Select your preferred language for translations in your feed.
</Trans> </Trans>
</Text> </Text>
<View style={[a.relative, web([a.w_full, {maxWidth: 400}])]}> <Select.Root
<RNPickerSelect value={langPrefs.primaryLanguage}
darkTheme={t.scheme === 'dark'} onValueChange={onChangePrimaryLanguage}>
placeholder={{}} <Select.Trigger label={_(msg`Select primary language`)}>
value={langPrefs.primaryLanguage} <Select.ValueText />
onValueChange={onChangePrimaryLanguage} <Select.Icon />
items={LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({ </Select.Trigger>
<Select.Content
renderItem={({label, value}) => (
<Select.Item value={value} label={label}>
<Select.ItemIndicator />
<Select.ItemText>{label}</Select.ItemText>
</Select.Item>
)}
items={DEDUPED_LANGUAGES.map(l => ({
label: languageName(l, langPrefs.appLanguage), label: languageName(l, langPrefs.appLanguage),
value: l.code2, value: l.code2,
key: l.code2 + l.code3,
}))} }))}
style={{
inputAndroid: {
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
color: t.atoms.text.color,
fontSize: 14,
letterSpacing: 0.5,
fontWeight: a.font_bold.fontWeight,
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: a.rounded_xs.borderRadius,
},
inputIOS: {
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
color: t.atoms.text.color,
fontSize: 14,
letterSpacing: 0.5,
fontWeight: a.font_bold.fontWeight,
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: a.rounded_xs.borderRadius,
},
inputWeb: {
flex: 1,
width: '100%',
cursor: 'pointer',
// @ts-ignore web only
'-moz-appearance': 'none',
'-webkit-appearance': 'none',
appearance: 'none',
outline: 0,
borderWidth: 0,
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
color: t.atoms.text.color,
fontSize: 14,
fontFamily: 'inherit',
letterSpacing: 0.5,
fontWeight: a.font_bold.fontWeight,
paddingHorizontal: 14,
paddingVertical: 8,
borderRadius: a.rounded_xs.borderRadius,
},
}}
/> />
</Select.Root>
<View
style={{
position: 'absolute',
top: 1,
right: 1,
bottom: 1,
width: 40,
backgroundColor: t.atoms.bg_contrast_25.backgroundColor,
borderRadius: a.rounded_xs.borderRadius,
pointerEvents: 'none',
alignItems: 'center',
justifyContent: 'center',
}}>
<ChevronDownIcon style={t.atoms.text} />
</View>
</View>
</View> </View>
</SettingsList.Group> </SettingsList.Group>
<SettingsList.Divider /> <SettingsList.Divider />
+6 -2
View File
@@ -184,10 +184,14 @@ export function Signup({onPressBack}: {onPressBack: () => void}) {
<Divider /> <Divider />
<View <View
style={[a.w_full, a.py_lg, a.flex_row, a.gap_lg, a.align_center]}> style={[a.w_full, a.py_lg, a.flex_row, a.gap_md, a.align_center]}>
<AppLanguageDropdown /> <AppLanguageDropdown />
<Text <Text
style={[t.atoms.text_contrast_medium, !gtMobile && a.text_md]}> style={[
a.flex_1,
t.atoms.text_contrast_medium,
!gtMobile && a.text_md,
]}>
<Trans>Having trouble?</Trans>{' '} <Trans>Having trouble?</Trans>{' '}
<InlineLinkText <InlineLinkText
label={_(msg`Contact support`)} label={_(msg`Contact support`)}
+8
View File
@@ -325,3 +325,11 @@ input[type='range'][orient='vertical']::-moz-range-thumb {
background-color: var(--backgroundLight); background-color: var(--backgroundLight);
border-color: transparent; border-color: transparent;
} }
/* #/components/Select/index.web.tsx */
.radix-select-content {
box-shadow: 0px 6px 24px -10px rgba(22, 23, 24, 0.25),
0px 6px 12px -12px rgba(22, 23, 24, 0.15);
min-width: var(--radix-select-trigger-width);
max-height: var(--radix-select-content-available-height);
}
+3 -1
View File
@@ -78,7 +78,9 @@ export const SplashScreen = ({
a.justify_center, a.justify_center,
a.align_center, a.align_center,
]}> ]}>
<AppLanguageDropdown /> <View>
<AppLanguageDropdown />
</View>
</View> </View>
<View style={{height: insets.bottom}} /> <View style={{height: insets.bottom}} />
</ErrorBoundary> </ErrorBoundary>
+3 -1
View File
@@ -154,9 +154,11 @@ function Footer() {
a.absolute, a.absolute,
a.inset_0, a.inset_0,
{top: 'auto'}, {top: 'auto'},
a.p_xl, a.px_xl,
a.py_lg,
a.border_t, a.border_t,
a.flex_row, a.flex_row,
a.align_center,
a.flex_wrap, a.flex_wrap,
a.gap_xl, a.gap_xl,
a.flex_1, a.flex_1,
+15 -14
View File
@@ -1,6 +1,5 @@
import {useCallback, useState} from 'react' import {useCallback, useState} from 'react'
import {Keyboard, StyleProp, View, ViewStyle} from 'react-native' import {Keyboard, type StyleProp, View, type ViewStyle} from 'react-native'
import RNPickerSelect from 'react-native-picker-select'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -240,19 +239,21 @@ function SubtitleFileRow({
numberOfLines={1}> numberOfLines={1}>
{file.name} {file.name}
</Text> </Text>
<RNPickerSelect <select
placeholder={{
label: _(msg`Select language...`),
value: '',
}}
value={language} value={language}
onValueChange={handleValueChange} onChange={evt => handleValueChange(evt.target.value)}
items={otherLanguages.map(lang => ({ style={{maxWidth: 200, flex: 1}}>
label: `${lang.name} (${langCode(lang)})`, <option value="" disabled selected hidden>
value: langCode(lang), {/* eslint-disable-next-line bsky-internal/avoid-unwrapped-text */}
}))} <Trans>Select language...</Trans>
style={{viewContainer: {maxWidth: 200, flex: 1}}} </option>
/> {otherLanguages.map(lang => (
<option key={langCode(lang)} value={langCode(lang)}>
{/* eslint-disable-next-line bsky-internal/avoid-unwrapped-text */}
{`${lang.name} (${langCode(lang)})`}
</option>
))}
</select>
</View> </View>
</View> </View>
+82 -32
View File
@@ -1,15 +1,20 @@
import React, {memo} from 'react' import React, {memo, useCallback} from 'react'
import { import {
ActivityIndicator, ActivityIndicator,
AppState, AppState,
Dimensions, Dimensions,
LayoutAnimation,
type ListRenderItemInfo, type ListRenderItemInfo,
type StyleProp, type StyleProp,
StyleSheet, StyleSheet,
View, View,
type ViewStyle, type ViewStyle,
} from 'react-native' } from 'react-native'
import {type AppBskyActorDefs, AppBskyEmbedVideo} from '@atproto/api' import {
type AppBskyActorDefs,
AppBskyEmbedVideo,
type AppBskyFeedDefs,
} from '@atproto/api'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
@@ -51,6 +56,7 @@ import {DiscoverFallbackHeader} from './DiscoverFallbackHeader'
import {FeedShutdownMsg} from './FeedShutdownMsg' import {FeedShutdownMsg} from './FeedShutdownMsg'
import {PostFeedErrorMessage} from './PostFeedErrorMessage' import {PostFeedErrorMessage} from './PostFeedErrorMessage'
import {PostFeedItem} from './PostFeedItem' import {PostFeedItem} from './PostFeedItem'
import {ShowLessFollowup} from './ShowLessFollowup'
import {ViewFullThread} from './ViewFullThread' import {ViewFullThread} from './ViewFullThread'
type FeedRow = type FeedRow =
@@ -117,6 +123,10 @@ type FeedRow =
type: 'interstitialTrendingVideos' type: 'interstitialTrendingVideos'
key: string key: string
} }
| {
type: 'showLessFollowup'
key: string
}
export function getItemsForFeedback(feedRow: FeedRow): export function getItemsForFeedback(feedRow: FeedRow):
| { | {
@@ -200,6 +210,20 @@ let PostFeed = ({
const {rightNavVisible} = useLayoutBreakpoints() const {rightNavVisible} = useLayoutBreakpoints()
const areVideoFeedsEnabled = isNative const areVideoFeedsEnabled = isNative
const [hasPressedShowLessUris, setHasPressedShowLessUris] = React.useState(
() => new Set<string>(),
)
const onPressShowLess = useCallback(
(interaction: AppBskyFeedDefs.Interaction) => {
if (interaction.item) {
const uri = interaction.item
setHasPressedShowLessUris(prev => new Set([...prev, uri]))
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
}
},
[],
)
const feedCacheKey = feedParams?.feedCacheKey const feedCacheKey = feedParams?.feedCacheKey
const opts = React.useMemo( const opts = React.useMemo(
() => ({enabled, ignoreFilterFor}), () => ({enabled, ignoreFilterFor}),
@@ -321,6 +345,19 @@ let PostFeed = ({
const {trendingDisabled, trendingVideoDisabled} = useTrendingSettings() const {trendingDisabled, trendingVideoDisabled} = useTrendingSettings()
const feedItems: FeedRow[] = React.useMemo(() => { const feedItems: FeedRow[] = React.useMemo(() => {
// wraps a slice item, and replaces it with a showLessFollowup item
// if the user has pressed show less on it
const sliceItem = (row: Extract<FeedRow, {type: 'sliceItem'}>) => {
if (hasPressedShowLessUris.has(row.slice.items[row.indexInSlice]?.uri)) {
return {
type: 'showLessFollowup',
key: row.key,
} as const
} else {
return row
}
}
let feedKind: 'following' | 'discover' | 'profile' | 'thevids' | undefined let feedKind: 'following' | 'discover' | 'profile' | 'thevids' | undefined
if (feedType === 'following') { if (feedType === 'following') {
feedKind = 'following' feedKind = 'following'
@@ -450,43 +487,51 @@ let PostFeed = ({
} else if (slice.isIncompleteThread && slice.items.length >= 3) { } else if (slice.isIncompleteThread && slice.items.length >= 3) {
const beforeLast = slice.items.length - 2 const beforeLast = slice.items.length - 2
const last = slice.items.length - 1 const last = slice.items.length - 1
arr.push({ arr.push(
type: 'sliceItem', sliceItem({
key: slice.items[0]._reactKey, type: 'sliceItem',
slice: slice, key: slice.items[0]._reactKey,
indexInSlice: 0, slice: slice,
showReplyTo: false, indexInSlice: 0,
}) showReplyTo: false,
}),
)
arr.push({ arr.push({
type: 'sliceViewFullThread', type: 'sliceViewFullThread',
key: slice._reactKey + '-viewFullThread', key: slice._reactKey + '-viewFullThread',
uri: slice.items[0].uri, uri: slice.items[0].uri,
}) })
arr.push({ arr.push(
type: 'sliceItem', sliceItem({
key: slice.items[beforeLast]._reactKey, type: 'sliceItem',
slice: slice, key: slice.items[beforeLast]._reactKey,
indexInSlice: beforeLast, slice: slice,
showReplyTo: indexInSlice: beforeLast,
slice.items[beforeLast].parentAuthor?.did !== showReplyTo:
slice.items[beforeLast].post.author.did, slice.items[beforeLast].parentAuthor?.did !==
}) slice.items[beforeLast].post.author.did,
arr.push({ }),
type: 'sliceItem', )
key: slice.items[last]._reactKey, arr.push(
slice: slice, sliceItem({
indexInSlice: last, type: 'sliceItem',
showReplyTo: false, key: slice.items[last]._reactKey,
}) slice: slice,
indexInSlice: last,
showReplyTo: false,
}),
)
} else { } else {
for (let i = 0; i < slice.items.length; i++) { for (let i = 0; i < slice.items.length; i++) {
arr.push({ arr.push(
type: 'sliceItem', sliceItem({
key: slice.items[i]._reactKey, type: 'sliceItem',
slice: slice, key: slice.items[i]._reactKey,
indexInSlice: i, slice: slice,
showReplyTo: i === 0, indexInSlice: i,
}) showReplyTo: i === 0,
}),
)
} }
} }
} }
@@ -531,6 +576,7 @@ let PostFeed = ({
gtMobile, gtMobile,
isVideoFeed, isVideoFeed,
areVideoFeedsEnabled, areVideoFeedsEnabled,
hasPressedShowLessUris,
]) ])
// events // events
@@ -650,6 +696,7 @@ let PostFeed = ({
isParentNotFound={item.isParentNotFound} isParentNotFound={item.isParentNotFound}
hideTopBorder={rowIndex === 0 && indexInSlice === 0} hideTopBorder={rowIndex === 0 && indexInSlice === 0}
rootPost={slice.items[0].post} rootPost={slice.items[0].post}
onShowLess={onPressShowLess}
/> />
) )
} else if (row.type === 'sliceViewFullThread') { } else if (row.type === 'sliceViewFullThread') {
@@ -684,6 +731,8 @@ let PostFeed = ({
sourceContext={sourceContext} sourceContext={sourceContext}
/> />
) )
} else if (row.type === 'showLessFollowup') {
return <ShowLessFollowup />
} else { } else {
return null return null
} }
@@ -700,6 +749,7 @@ let PostFeed = ({
feedUriOrActorDid, feedUriOrActorDid,
feedTab, feedTab,
feedCacheKey, feedCacheKey,
onPressShowLess,
], ],
) )
+26 -15
View File
@@ -1,23 +1,23 @@
import React, {memo, useMemo, useState} from 'react' import {memo, useCallback, useMemo, useState} from 'react'
import {StyleSheet, View} from 'react-native' import {StyleSheet, View} from 'react-native'
import { import {
AppBskyActorDefs, type AppBskyActorDefs,
AppBskyFeedDefs, AppBskyFeedDefs,
AppBskyFeedPost, AppBskyFeedPost,
AppBskyFeedThreadgate, AppBskyFeedThreadgate,
AtUri, AtUri,
ModerationDecision, type ModerationDecision,
RichText as RichTextAPI, RichText as RichTextAPI,
} from '@atproto/api' } from '@atproto/api'
import { import {
FontAwesomeIcon, FontAwesomeIcon,
FontAwesomeIconStyle, type FontAwesomeIconStyle,
} from '@fortawesome/react-native-fontawesome' } from '@fortawesome/react-native-fontawesome'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useQueryClient} from '@tanstack/react-query' import {useQueryClient} from '@tanstack/react-query'
import {isReasonFeedSource, ReasonFeedSource} from '#/lib/api/feed/types' import {isReasonFeedSource, type ReasonFeedSource} from '#/lib/api/feed/types'
import {MAX_POST_LINES} from '#/lib/constants' import {MAX_POST_LINES} from '#/lib/constants'
import {usePalette} from '#/lib/hooks/usePalette' import {usePalette} from '#/lib/hooks/usePalette'
import {makeProfileLink} from '#/lib/routes/links' import {makeProfileLink} from '#/lib/routes/links'
@@ -25,7 +25,11 @@ import {sanitizeDisplayName} from '#/lib/strings/display-names'
import {sanitizeHandle} from '#/lib/strings/handles' import {sanitizeHandle} from '#/lib/strings/handles'
import {countLines} from '#/lib/strings/helpers' import {countLines} from '#/lib/strings/helpers'
import {s} from '#/lib/styles' import {s} from '#/lib/styles'
import {POST_TOMBSTONE, Shadow, usePostShadow} from '#/state/cache/post-shadow' import {
POST_TOMBSTONE,
type Shadow,
usePostShadow,
} from '#/state/cache/post-shadow'
import {useFeedFeedbackContext} from '#/state/feed-feedback' import {useFeedFeedbackContext} from '#/state/feed-feedback'
import {precacheProfile} from '#/state/queries/profile' import {precacheProfile} from '#/state/queries/profile'
import {useSession} from '#/state/session' import {useSession} from '#/state/session'
@@ -43,7 +47,7 @@ import {Repost_Stroke2_Corner2_Rounded as RepostIcon} from '#/components/icons/R
import {ContentHider} from '#/components/moderation/ContentHider' import {ContentHider} from '#/components/moderation/ContentHider'
import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe' import {LabelsOnMyPost} from '#/components/moderation/LabelsOnMe'
import {PostAlerts} from '#/components/moderation/PostAlerts' import {PostAlerts} from '#/components/moderation/PostAlerts'
import {AppModerationCause} from '#/components/Pills' import {type AppModerationCause} from '#/components/Pills'
import {ProfileHoverCard} from '#/components/ProfileHoverCard' import {ProfileHoverCard} from '#/components/ProfileHoverCard'
import {RichText} from '#/components/RichText' import {RichText} from '#/components/RichText'
import {SubtleWebHover} from '#/components/SubtleWebHover' import {SubtleWebHover} from '#/components/SubtleWebHover'
@@ -86,9 +90,11 @@ export function PostFeedItem({
isParentBlocked, isParentBlocked,
isParentNotFound, isParentNotFound,
rootPost, rootPost,
onShowLess,
}: FeedItemProps & { }: FeedItemProps & {
post: AppBskyFeedDefs.PostView post: AppBskyFeedDefs.PostView
rootPost: AppBskyFeedDefs.PostView rootPost: AppBskyFeedDefs.PostView
onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
}): React.ReactNode { }): React.ReactNode {
const postShadowed = usePostShadow(post) const postShadowed = usePostShadow(post)
const richText = useMemo( const richText = useMemo(
@@ -122,6 +128,7 @@ export function PostFeedItem({
isParentBlocked={isParentBlocked} isParentBlocked={isParentBlocked}
isParentNotFound={isParentNotFound} isParentNotFound={isParentNotFound}
rootPost={rootPost} rootPost={rootPost}
onShowLess={onShowLess}
/> />
) )
} }
@@ -144,23 +151,27 @@ let FeedItemInner = ({
isParentBlocked, isParentBlocked,
isParentNotFound, isParentNotFound,
rootPost, rootPost,
onShowLess,
}: FeedItemProps & { }: FeedItemProps & {
richText: RichTextAPI richText: RichTextAPI
post: Shadow<AppBskyFeedDefs.PostView> post: Shadow<AppBskyFeedDefs.PostView>
rootPost: AppBskyFeedDefs.PostView rootPost: AppBskyFeedDefs.PostView
onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
}): React.ReactNode => { }): React.ReactNode => {
const queryClient = useQueryClient() const queryClient = useQueryClient()
const {openComposer} = useComposerControls() const {openComposer} = useComposerControls()
const pal = usePalette('default') const pal = usePalette('default')
const {_} = useLingui() const {_} = useLingui()
const [hover, setHover] = useState(false)
const href = useMemo(() => { const href = useMemo(() => {
const urip = new AtUri(post.uri) const urip = new AtUri(post.uri)
return makeProfileLink(post.author, 'post', urip.rkey) return makeProfileLink(post.author, 'post', urip.rkey)
}, [post.uri, post.author]) }, [post.uri, post.author])
const {sendInteraction} = useFeedFeedbackContext() const {sendInteraction} = useFeedFeedbackContext()
const onPressReply = React.useCallback(() => { const onPressReply = useCallback(() => {
sendInteraction({ sendInteraction({
item: post.uri, item: post.uri,
event: 'app.bsky.feed.defs#interactionReply', event: 'app.bsky.feed.defs#interactionReply',
@@ -178,7 +189,7 @@ let FeedItemInner = ({
}) })
}, [post, record, openComposer, moderation, sendInteraction, feedContext]) }, [post, record, openComposer, moderation, sendInteraction, feedContext])
const onOpenAuthor = React.useCallback(() => { const onOpenAuthor = useCallback(() => {
sendInteraction({ sendInteraction({
item: post.uri, item: post.uri,
event: 'app.bsky.feed.defs#clickthroughAuthor', event: 'app.bsky.feed.defs#clickthroughAuthor',
@@ -186,7 +197,7 @@ let FeedItemInner = ({
}) })
}, [sendInteraction, post, feedContext]) }, [sendInteraction, post, feedContext])
const onOpenReposter = React.useCallback(() => { const onOpenReposter = useCallback(() => {
sendInteraction({ sendInteraction({
item: post.uri, item: post.uri,
event: 'app.bsky.feed.defs#clickthroughReposter', event: 'app.bsky.feed.defs#clickthroughReposter',
@@ -194,7 +205,7 @@ let FeedItemInner = ({
}) })
}, [sendInteraction, post, feedContext]) }, [sendInteraction, post, feedContext])
const onOpenEmbed = React.useCallback(() => { const onOpenEmbed = useCallback(() => {
sendInteraction({ sendInteraction({
item: post.uri, item: post.uri,
event: 'app.bsky.feed.defs#clickthroughEmbed', event: 'app.bsky.feed.defs#clickthroughEmbed',
@@ -202,7 +213,7 @@ let FeedItemInner = ({
}) })
}, [sendInteraction, post, feedContext]) }, [sendInteraction, post, feedContext])
const onBeforePress = React.useCallback(() => { const onBeforePress = useCallback(() => {
sendInteraction({ sendInteraction({
item: post.uri, item: post.uri,
event: 'app.bsky.feed.defs#clickthroughItem', event: 'app.bsky.feed.defs#clickthroughItem',
@@ -240,7 +251,6 @@ let FeedItemInner = ({
? rootPost.threadgate.record ? rootPost.threadgate.record
: undefined : undefined
const [hover, setHover] = useState(false)
return ( return (
<Link <Link
testID={`feedItem-by-${post.author.handle}`} testID={`feedItem-by-${post.author.handle}`}
@@ -427,6 +437,7 @@ let FeedItemInner = ({
logContext="FeedItem" logContext="FeedItem"
feedContext={feedContext} feedContext={feedContext}
threadgateRecord={threadgateRecord} threadgateRecord={threadgateRecord}
onShowLess={onShowLess}
/> />
</View> </View>
</View> </View>
@@ -461,7 +472,7 @@ let PostContent = ({
const threadgateHiddenReplies = useMergedThreadgateHiddenReplies({ const threadgateHiddenReplies = useMergedThreadgateHiddenReplies({
threadgateRecord, threadgateRecord,
}) })
const additionalPostAlerts: AppModerationCause[] = React.useMemo(() => { const additionalPostAlerts: AppModerationCause[] = useMemo(() => {
const isPostHiddenByThreadgate = threadgateHiddenReplies.has(post.uri) const isPostHiddenByThreadgate = threadgateHiddenReplies.has(post.uri)
const rootPostUri = bsky.dangerousIsType<AppBskyFeedPost.Record>( const rootPostUri = bsky.dangerousIsType<AppBskyFeedPost.Record>(
post.record, post.record,
@@ -482,7 +493,7 @@ let PostContent = ({
: [] : []
}, [post, currentAccount?.did, threadgateHiddenReplies]) }, [post, currentAccount?.did, threadgateHiddenReplies])
const onPressShowMore = React.useCallback(() => { const onPressShowMore = useCallback(() => {
setLimitLines(false) setLimitLines(false)
}, [setLimitLines]) }, [setLimitLines])
+46
View File
@@ -0,0 +1,46 @@
import {View} from 'react-native'
import {Trans} from '@lingui/macro'
import {atoms as a, useTheme} from '#/alf'
import {CircleCheck_Stroke2_Corner0_Rounded} from '#/components/icons/CircleCheck'
import {Text} from '#/components/Typography'
export function ShowLessFollowup() {
const t = useTheme()
return (
<View
style={[
t.atoms.border_contrast_low,
a.border_t,
t.atoms.bg_contrast_25,
a.p_sm,
]}>
<View
style={[
t.atoms.bg,
t.atoms.border_contrast_low,
a.border,
a.rounded_sm,
a.p_md,
a.flex_row,
a.gap_sm,
]}>
<CircleCheck_Stroke2_Corner0_Rounded
style={[t.atoms.text_contrast_low]}
size="sm"
/>
<Text
style={[
a.flex_1,
a.text_sm,
t.atoms.text_contrast_medium,
a.leading_snug,
]}>
<Trans>
Thank you for your feedback! It has been sent to the feed operator.
</Trans>
</Text>
</View>
</View>
)
}
+10 -6
View File
@@ -1,4 +1,4 @@
import React, {memo, useMemo, useState} from 'react' import {memo, useMemo, useState} from 'react'
import { import {
Pressable, Pressable,
type PressableProps, type PressableProps,
@@ -6,16 +6,17 @@ import {
type ViewStyle, type ViewStyle,
} from 'react-native' } from 'react-native'
import { import {
AppBskyFeedDefs, type AppBskyFeedDefs,
AppBskyFeedPost, type AppBskyFeedPost,
AppBskyFeedThreadgate, type AppBskyFeedThreadgate,
RichText as RichTextAPI, type RichText as RichTextAPI,
} from '@atproto/api' } from '@atproto/api'
import {msg} from '@lingui/macro' import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import type React from 'react'
import {useTheme} from '#/lib/ThemeContext' import {useTheme} from '#/lib/ThemeContext'
import {Shadow} from '#/state/cache/post-shadow' import {type Shadow} from '#/state/cache/post-shadow'
import {atoms as a, useTheme as useAlf} from '#/alf' import {atoms as a, useTheme as useAlf} from '#/alf'
import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid' import {DotGrid_Stroke2_Corner0_Rounded as DotsHorizontal} from '#/components/icons/DotGrid'
import {useMenuControl} from '#/components/Menu' import {useMenuControl} from '#/components/Menu'
@@ -34,6 +35,7 @@ let PostDropdownBtn = ({
size, size,
timestamp, timestamp,
threadgateRecord, threadgateRecord,
onShowLess,
}: { }: {
testID: string testID: string
post: Shadow<AppBskyFeedDefs.PostView> post: Shadow<AppBskyFeedDefs.PostView>
@@ -45,6 +47,7 @@ let PostDropdownBtn = ({
size?: 'lg' | 'md' | 'sm' size?: 'lg' | 'md' | 'sm'
timestamp: string timestamp: string
threadgateRecord?: AppBskyFeedThreadgate.Record threadgateRecord?: AppBskyFeedThreadgate.Record
onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
}): React.ReactNode => { }): React.ReactNode => {
const theme = useTheme() const theme = useTheme()
const alf = useAlf() const alf = useAlf()
@@ -100,6 +103,7 @@ let PostDropdownBtn = ({
richText={richText} richText={richText}
timestamp={timestamp} timestamp={timestamp}
threadgateRecord={threadgateRecord} threadgateRecord={threadgateRecord}
onShowLess={onShowLess}
/> />
)} )}
</Menu.Root> </Menu.Root>
@@ -17,6 +17,8 @@ import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import {useNavigation} from '@react-navigation/native' import {useNavigation} from '@react-navigation/native'
import {IS_INTERNAL} from '#/lib/app-info'
import {DISCOVER_DEBUG_DIDS} from '#/lib/constants'
import {useOpenLink} from '#/lib/hooks/useOpenLink' import {useOpenLink} from '#/lib/hooks/useOpenLink'
import {getCurrentRoute} from '#/lib/routes/helpers' import {getCurrentRoute} from '#/lib/routes/helpers'
import {makeProfileLink} from '#/lib/routes/links' import {makeProfileLink} from '#/lib/routes/links'
@@ -60,6 +62,7 @@ import {
} from '#/components/dialogs/PostInteractionSettingsDialog' } from '#/components/dialogs/PostInteractionSettingsDialog'
import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog' import {SendViaChatDialog} from '#/components/dms/dialogs/ShareViaChatDialog'
import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox' import {ArrowOutOfBox_Stroke2_Corner0_Rounded as Share} from '#/components/icons/ArrowOutOfBox'
import {Atom_Stroke2_Corner0_Rounded as AtomIcon} from '#/components/icons/Atom'
import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble' import {BubbleQuestion_Stroke2_Corner0_Rounded as Translate} from '#/components/icons/Bubble'
import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard' import {Clipboard_Stroke2_Corner2_Rounded as ClipboardIcon} from '#/components/icons/Clipboard'
import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBrackets} from '#/components/icons/CodeBrackets' import {CodeBrackets_Stroke2_Corner0_Rounded as CodeBrackets} from '#/components/icons/CodeBrackets'
@@ -98,6 +101,7 @@ let PostDropdownMenuItems = ({
richText, richText,
timestamp, timestamp,
threadgateRecord, threadgateRecord,
onShowLess,
}: { }: {
testID: string testID: string
post: Shadow<AppBskyFeedDefs.PostView> post: Shadow<AppBskyFeedDefs.PostView>
@@ -109,6 +113,7 @@ let PostDropdownMenuItems = ({
size?: 'lg' | 'md' | 'sm' size?: 'lg' | 'md' | 'sm'
timestamp: string timestamp: string
threadgateRecord?: AppBskyFeedThreadgate.Record threadgateRecord?: AppBskyFeedThreadgate.Record
onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
}): React.ReactNode => { }): React.ReactNode => {
const {hasSession, currentAccount} = useSession() const {hasSession, currentAccount} = useSession()
const {gtMobile} = useBreakpoints() const {gtMobile} = useBreakpoints()
@@ -300,8 +305,15 @@ let PostDropdownMenuItems = ({
item: postUri, item: postUri,
feedContext: postFeedContext, feedContext: postFeedContext,
}) })
Toast.show(_(msg({message: 'Feedback sent!', context: 'toast'}))) if (onShowLess) {
}, [feedFeedback, postUri, postFeedContext, _]) onShowLess({
item: postUri,
feedContext: postFeedContext,
})
} else {
Toast.show(_(msg({message: 'Feedback sent!', context: 'toast'})))
}
}, [feedFeedback, postUri, postFeedContext, _, onShowLess])
const onSelectChatToShareTo = React.useCallback( const onSelectChatToShareTo = React.useCallback(
(conversation: string) => { (conversation: string) => {
@@ -430,6 +442,13 @@ let PostDropdownMenuItems = ({
shareText(postAuthor.did) shareText(postAuthor.did)
}, [postAuthor.did]) }, [postAuthor.did])
const onReportMisclassification = useCallback(() => {
const url = `https://docs.google.com/forms/d/e/1FAIpQLSd0QPqhNFksDQf1YyOos7r1ofCLvmrKAH1lU042TaS3GAZaWQ/viewform?entry.1756031717=${toShareUrl(
href,
)}`
openLink(url)
}, [href, openLink])
return ( return (
<> <>
<Menu.Outer> <Menu.Outer>
@@ -539,6 +558,19 @@ let PostDropdownMenuItems = ({
<Menu.ItemText>{_(msg`Show less like this`)}</Menu.ItemText> <Menu.ItemText>{_(msg`Show less like this`)}</Menu.ItemText>
<Menu.ItemIcon icon={EmojiSad} position="right" /> <Menu.ItemIcon icon={EmojiSad} position="right" />
</Menu.Item> </Menu.Item>
{IS_INTERNAL &&
DISCOVER_DEBUG_DIDS[currentAccount?.did ?? ''] && (
<Menu.Item
testID="postDropdownReportMisclassificationBtn"
label={_(msg`Report topic misclassification`)}
onPress={onReportMisclassification}>
<Menu.ItemText>
{_(msg`Report topic misclassification`)}
</Menu.ItemText>
<Menu.ItemIcon icon={AtomIcon} position="right" />
</Menu.Item>
)}
</Menu.Group> </Menu.Group>
</> </>
)} )}
+8 -5
View File
@@ -8,11 +8,11 @@ import {
} from 'react-native' } from 'react-native'
import * as Clipboard from 'expo-clipboard' import * as Clipboard from 'expo-clipboard'
import { import {
AppBskyFeedDefs, type AppBskyFeedDefs,
AppBskyFeedPost, type AppBskyFeedPost,
AppBskyFeedThreadgate, type AppBskyFeedThreadgate,
AtUri, AtUri,
RichText as RichTextAPI, type RichText as RichTextAPI,
} from '@atproto/api' } from '@atproto/api'
import {msg, plural} from '@lingui/macro' import {msg, plural} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
@@ -26,7 +26,7 @@ import {makeProfileLink} from '#/lib/routes/links'
import {shareUrl} from '#/lib/sharing' import {shareUrl} from '#/lib/sharing'
import {useGate} from '#/lib/statsig/statsig' import {useGate} from '#/lib/statsig/statsig'
import {toShareUrl} from '#/lib/strings/url-helpers' import {toShareUrl} from '#/lib/strings/url-helpers'
import {Shadow} from '#/state/cache/types' import {type Shadow} from '#/state/cache/types'
import {useFeedFeedbackContext} from '#/state/feed-feedback' import {useFeedFeedbackContext} from '#/state/feed-feedback'
import { import {
usePostLikeMutationQueue, usePostLikeMutationQueue,
@@ -60,6 +60,7 @@ let PostCtrls = ({
onPostReply, onPostReply,
logContext, logContext,
threadgateRecord, threadgateRecord,
onShowLess,
}: { }: {
big?: boolean big?: boolean
post: Shadow<AppBskyFeedDefs.PostView> post: Shadow<AppBskyFeedDefs.PostView>
@@ -71,6 +72,7 @@ let PostCtrls = ({
onPostReply?: (postUri: string | undefined) => void onPostReply?: (postUri: string | undefined) => void
logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo' logContext: 'FeedItem' | 'PostThreadItem' | 'Post' | 'ImmersiveVideo'
threadgateRecord?: AppBskyFeedThreadgate.Record threadgateRecord?: AppBskyFeedThreadgate.Record
onShowLess?: (interaction: AppBskyFeedDefs.Interaction) => void
}): React.ReactNode => { }): React.ReactNode => {
const t = useTheme() const t = useTheme()
const {_, i18n} = useLingui() const {_, i18n} = useLingui()
@@ -378,6 +380,7 @@ let PostCtrls = ({
hitSlop={POST_CTRL_HITSLOP} hitSlop={POST_CTRL_HITSLOP}
timestamp={post.indexedAt} timestamp={post.indexedAt}
threadgateRecord={threadgateRecord} threadgateRecord={threadgateRecord}
onShowLess={onShowLess}
/> />
</View> </View>
{isDiscoverDebugUser && feedContext && ( {isDiscoverDebugUser && feedContext && (
+1 -1
View File
@@ -65,7 +65,7 @@ let NavSignupCard = ({}: {}): React.ReactNode => {
</View> </View>
<View style={[a.mt_md, a.w_full, {height: 32}]}> <View style={[a.mt_md, a.w_full, {height: 32}]}>
<AppLanguageDropdown style={{marginTop: 0}} /> <AppLanguageDropdown />
</View> </View>
</View> </View>
) )
+1 -1
View File
@@ -137,7 +137,7 @@ export function DesktopRightNav({routeName}: {routeName: string}) {
{!hasSession && leftNavMinimal && ( {!hasSession && leftNavMinimal && (
<View style={[a.w_full, {height: 32}]}> <View style={[a.w_full, {height: 32}]}>
<AppLanguageDropdown style={{marginTop: 0}} /> <AppLanguageDropdown />
</View> </View>
)} )}
</View> </View>
-13
View File
@@ -14290,11 +14290,6 @@ lodash.isequal@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ== integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
lodash.isobject@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d"
integrity sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==
lodash.memoize@^4.1.2: lodash.memoize@^4.1.2:
version "4.1.2" version "4.1.2"
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
@@ -16840,14 +16835,6 @@ react-native-pager-view@6.7.1:
resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-6.7.1.tgz#60d52dedbcc92ee7037a13287ebeed5f74e49df7" resolved "https://registry.yarnpkg.com/react-native-pager-view/-/react-native-pager-view-6.7.1.tgz#60d52dedbcc92ee7037a13287ebeed5f74e49df7"
integrity sha512-cBSr6xw4g5N7Kd3VGWcf+kmaH7iBWb0DXAf2bVo3bXkzBcBbTOmYSvc0LVLHhUPW8nEq5WjT9LCIYAzgF++EXw== integrity sha512-cBSr6xw4g5N7Kd3VGWcf+kmaH7iBWb0DXAf2bVo3bXkzBcBbTOmYSvc0LVLHhUPW8nEq5WjT9LCIYAzgF++EXw==
react-native-picker-select@^9.3.1:
version "9.3.1"
resolved "https://registry.yarnpkg.com/react-native-picker-select/-/react-native-picker-select-9.3.1.tgz#8a2ad51c286fcd54ef60fb883842ec1895c15003"
integrity sha512-o621HcsKJfJkpYeP/PZQiZTKbf8W7FT08niLFL0v1pGkIQyak5IfzfinV2t+/l1vktGwAH2Tt29LrP/Hc5fk3A==
dependencies:
lodash.isequal "^4.5.0"
lodash.isobject "^3.0.2"
react-native-progress@bluesky-social/react-native-progress: react-native-progress@bluesky-social/react-native-progress:
version "5.0.0" version "5.0.0"
resolved "https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4" resolved "https://codeload.github.com/bluesky-social/react-native-progress/tar.gz/5a372f4f2ce5feb26f4f47b6a4d187ab9b923ab4"