bump
This commit is contained in:
@@ -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? {
|
func tryFindScrollView() {
|
||||||
var foundCount = foundCount
|
guard let scrollViewTag = scrollViewTag else {
|
||||||
if let sv = view as? UIScrollView { return sv }
|
return
|
||||||
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() {
|
|
||||||
// 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: couldn’t 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() {
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
}
|
||||||
@@ -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 ""
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
|
||||||
darkTheme={t.scheme === 'dark'}
|
|
||||||
placeholder={{}}
|
|
||||||
value={sanitizeAppLanguageSetting(langPrefs.appLanguage)}
|
value={sanitizeAppLanguageSetting(langPrefs.appLanguage)}
|
||||||
onValueChange={onChangeAppLanguage}
|
onValueChange={onChangeAppLanguage}>
|
||||||
items={APP_LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({
|
<Select.Trigger label={_(msg`Select app language`)}>
|
||||||
|
<Select.ValueText />
|
||||||
|
<Select.Icon />
|
||||||
|
</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
|
|
||||||
darkTheme={t.scheme === 'dark'}
|
|
||||||
placeholder={{}}
|
|
||||||
value={langPrefs.primaryLanguage}
|
value={langPrefs.primaryLanguage}
|
||||||
onValueChange={onChangePrimaryLanguage}
|
onValueChange={onChangePrimaryLanguage}>
|
||||||
items={LANGUAGES.filter(l => Boolean(l.code2)).map(l => ({
|
<Select.Trigger label={_(msg`Select primary language`)}>
|
||||||
|
<Select.ValueText />
|
||||||
|
<Select.Icon />
|
||||||
|
</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 />
|
||||||
|
|||||||
@@ -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`)}
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -78,8 +78,10 @@ export const SplashScreen = ({
|
|||||||
a.justify_center,
|
a.justify_center,
|
||||||
a.align_center,
|
a.align_center,
|
||||||
]}>
|
]}>
|
||||||
|
<View>
|
||||||
<AppLanguageDropdown />
|
<AppLanguageDropdown />
|
||||||
</View>
|
</View>
|
||||||
|
</View>
|
||||||
<View style={{height: insets.bottom}} />
|
<View style={{height: insets.bottom}} />
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</CenteredView>
|
</CenteredView>
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|
||||||
|
|||||||
@@ -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,19 +487,22 @@ 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(
|
||||||
|
sliceItem({
|
||||||
type: 'sliceItem',
|
type: 'sliceItem',
|
||||||
key: slice.items[0]._reactKey,
|
key: slice.items[0]._reactKey,
|
||||||
slice: slice,
|
slice: slice,
|
||||||
indexInSlice: 0,
|
indexInSlice: 0,
|
||||||
showReplyTo: false,
|
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(
|
||||||
|
sliceItem({
|
||||||
type: 'sliceItem',
|
type: 'sliceItem',
|
||||||
key: slice.items[beforeLast]._reactKey,
|
key: slice.items[beforeLast]._reactKey,
|
||||||
slice: slice,
|
slice: slice,
|
||||||
@@ -470,23 +510,28 @@ let PostFeed = ({
|
|||||||
showReplyTo:
|
showReplyTo:
|
||||||
slice.items[beforeLast].parentAuthor?.did !==
|
slice.items[beforeLast].parentAuthor?.did !==
|
||||||
slice.items[beforeLast].post.author.did,
|
slice.items[beforeLast].post.author.did,
|
||||||
})
|
}),
|
||||||
arr.push({
|
)
|
||||||
|
arr.push(
|
||||||
|
sliceItem({
|
||||||
type: 'sliceItem',
|
type: 'sliceItem',
|
||||||
key: slice.items[last]._reactKey,
|
key: slice.items[last]._reactKey,
|
||||||
slice: slice,
|
slice: slice,
|
||||||
indexInSlice: last,
|
indexInSlice: last,
|
||||||
showReplyTo: false,
|
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(
|
||||||
|
sliceItem({
|
||||||
type: 'sliceItem',
|
type: 'sliceItem',
|
||||||
key: slice.items[i]._reactKey,
|
key: slice.items[i]._reactKey,
|
||||||
slice: slice,
|
slice: slice,
|
||||||
indexInSlice: i,
|
indexInSlice: i,
|
||||||
showReplyTo: i === 0,
|
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,
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -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])
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
})
|
})
|
||||||
|
if (onShowLess) {
|
||||||
|
onShowLess({
|
||||||
|
item: postUri,
|
||||||
|
feedContext: postFeedContext,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
Toast.show(_(msg({message: 'Feedback sent!', context: 'toast'})))
|
Toast.show(_(msg({message: 'Feedback sent!', context: 'toast'})))
|
||||||
}, [feedFeedback, postUri, postFeedContext, _])
|
}
|
||||||
|
}, [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,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 && (
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user