Merge branch 'bluesky-social:main' into patch-3

This commit is contained in:
Minseo Lee 2024-03-13 10:30:07 +09:00 committed by GitHub
commit 3ead08ab26
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 1575 additions and 1382 deletions

View file

@ -75,6 +75,7 @@ export function Outer({
control,
onClose,
nativeOptions,
testID,
}: React.PropsWithChildren<DialogOuterProps>) {
const t = useTheme()
const sheet = React.useRef<BottomSheet>(null)
@ -145,7 +146,8 @@ export function Outer({
accessibilityViewIsModal
// Android
importantForAccessibility="yes"
style={[a.absolute, a.inset_0]}>
style={[a.absolute, a.inset_0]}
testID={testID}>
<BottomSheet
enableDynamicSizing={!hasSnapPoints}
enablePanDownToClose

View file

@ -46,6 +46,7 @@ export type DialogOuterProps = {
sheet?: Omit<BottomSheetProps, 'children'>
}
webOptions?: {}
testID?: string
}
type DialogInnerPropsBase<T> = React.PropsWithChildren<ViewStyleProp> & T

View file

@ -1,17 +1,13 @@
import React from 'react'
import {GestureResponderEvent} from 'react-native'
import {
useLinkProps,
useNavigation,
StackActions,
} from '@react-navigation/native'
import {useLinkProps, StackActions} from '@react-navigation/native'
import {sanitizeUrl} from '@braintree/sanitize-url'
import {useInteractionState} from '#/components/hooks/useInteractionState'
import {isWeb} from '#/platform/detection'
import {useTheme, web, flatten, TextStyleProp, atoms as a} from '#/alf'
import {Button, ButtonProps} from '#/components/Button'
import {AllNavigatorParams, NavigationProp} from '#/lib/routes/types'
import {AllNavigatorParams} from '#/lib/routes/types'
import {
convertBskyAppUrlIfNeeded,
isExternalUrl,
@ -21,6 +17,7 @@ import {useModalControls} from '#/state/modals'
import {router} from '#/routes'
import {Text, TextProps} from '#/components/Typography'
import {useOpenLink} from 'state/preferences/in-app-browser'
import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped'
/**
* Only available within a `Link`, since that inherits from `Button`.
@ -74,7 +71,7 @@ export function useLink({
}: BaseLinkProps & {
displayText: string
}) {
const navigation = useNavigation<NavigationProp>()
const navigation = useNavigationDeduped()
const {href} = useLinkProps<AllNavigatorParams>({
to:
typeof to === 'string' ? convertBskyAppUrlIfNeeded(sanitizeUrl(to)) : to,
@ -231,6 +228,7 @@ export function InlineLink({
onPress: outerOnPress,
download,
selectable,
label,
...rest
}: InlineLinkProps) {
const t = useTheme()
@ -258,7 +256,8 @@ export function InlineLink({
return (
<Text
selectable={selectable}
label={href}
accessibilityHint=""
accessibilityLabel={label || href}
{...rest}
style={[
{color: t.palette.primary_500},

View file

@ -9,9 +9,8 @@ import {cleanError} from 'lib/strings/errors'
import {Button} from '#/components/Button'
import {Text} from '#/components/Typography'
import {StackActions} from '@react-navigation/native'
import {useNavigation} from '@react-navigation/core'
import {NavigationProp} from 'lib/routes/types'
import {router} from '#/routes'
import {useNavigationDeduped} from 'lib/hooks/useNavigationDeduped'
export function ListFooter({
isFetching,
@ -144,7 +143,7 @@ export function ListMaybePlaceholder({
notFoundType?: 'page' | 'results'
onRetry?: () => Promise<unknown>
}) {
const navigation = useNavigation<NavigationProp>()
const navigation = useNavigationDeduped()
const t = useTheme()
const {gtMobile, gtTablet} = useBreakpoints()
const {_} = useLingui()

View file

@ -1,5 +1,5 @@
import React from 'react'
import {View, Pressable} from 'react-native'
import {View, Pressable, ViewStyle, StyleProp} from 'react-native'
import flattenReactChildren from 'react-keyed-flatten-children'
import {atoms as a, useTheme} from '#/alf'
@ -16,6 +16,10 @@ import {
ItemTextProps,
ItemIconProps,
} from '#/components/Menu/types'
import {Button, ButtonText} from '#/components/Button'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {isNative} from 'platform/detection'
export {useDialogControl as useMenuControl} from '#/components/Dialog'
@ -68,7 +72,13 @@ export function Trigger({children, label}: TriggerProps) {
})
}
export function Outer({children}: React.PropsWithChildren<{}>) {
export function Outer({
children,
showCancel,
}: React.PropsWithChildren<{
showCancel?: boolean
style?: StyleProp<ViewStyle>
}>) {
const context = React.useContext(Context)
return (
@ -78,7 +88,10 @@ export function Outer({children}: React.PropsWithChildren<{}>) {
{/* Re-wrap with context since Dialogs are portal-ed to root */}
<Context.Provider value={context}>
<Dialog.ScrollableInner label="Menu TODO">
<View style={[a.gap_lg]}>{children}</View>
<View style={[a.gap_lg]}>
{children}
{isNative && showCancel && <Cancel />}
</View>
<View style={{height: a.gap_lg.gap}} />
</Dialog.ScrollableInner>
</Context.Provider>
@ -185,6 +198,22 @@ export function Group({children, style}: GroupProps) {
)
}
function Cancel() {
const {_} = useLingui()
const {control} = React.useContext(Context)
return (
<Button
label={_(msg`Close this dialog`)}
size="small"
variant="ghost"
color="secondary"
onPress={() => control.close()}>
<ButtonText>Cancel</ButtonText>
</Button>
)
}
export function Divider() {
return null
}

View file

@ -1,6 +1,10 @@
/* eslint-disable react/prop-types */
import React from 'react'
import {View, Pressable} from 'react-native'
import {View, Pressable, ViewStyle, StyleProp} from 'react-native'
import * as DropdownMenu from '@radix-ui/react-dropdown-menu'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import * as Dialog from '#/components/Dialog'
import {useInteractionState} from '#/components/hooks/useInteractionState'
@ -14,8 +18,10 @@ import {
GroupProps,
ItemTextProps,
ItemIconProps,
RadixPassThroughTriggerProps,
} from '#/components/Menu/types'
import {Context} from '#/components/Menu/context'
import {Portal} from '#/components/Portal'
export function useMenuControl(): Dialog.DialogControlProps {
const id = React.useId()
@ -47,6 +53,7 @@ export function Root({
}: React.PropsWithChildren<{
control?: Dialog.DialogOuterProps['control']
}>) {
const {_} = useLingui()
const defaultControl = useMenuControl()
const context = React.useMemo<ContextType>(
() => ({
@ -67,6 +74,18 @@ export function Root({
return (
<Context.Provider value={context}>
{context.control.isOpen && (
<Portal>
<Pressable
style={[a.fixed, a.inset_0, a.z_50]}
onPress={() => context.control.close()}
accessibilityHint=""
accessibilityLabel={_(
msg`Context menu backdrop, click to close the menu.`,
)}
/>
</Portal>
)}
<DropdownMenu.Root
open={context.control.isOpen}
onOpenChange={onOpenChange}>
@ -76,7 +95,24 @@ export function Root({
)
}
export function Trigger({children, label, style}: TriggerProps) {
const RadixTriggerPassThrough = React.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 {control} = React.useContext(Context)
const {
state: hovered,
@ -87,33 +123,42 @@ export function Trigger({children, label, style}: TriggerProps) {
return (
<DropdownMenu.Trigger asChild>
<Pressable
accessibilityHint=""
accessibilityLabel={label}
onFocus={onFocus}
onBlur={onBlur}
style={flatten([style, focused && web({outline: 0})])}
onPointerDown={() => control.open()}
{...web({
onMouseEnter,
onMouseLeave,
})}>
{children({
isNative: false,
control,
state: {
hovered,
focused,
pressed: false,
},
props: {},
})}
</Pressable>
<RadixTriggerPassThrough>
{props =>
children({
isNative: false,
control,
state: {
hovered,
focused,
pressed: false,
},
props: {
...props,
// disable on web, use `onPress`
onPointerDown: () => false,
onPress: () =>
control.isOpen ? control.close() : control.open(),
onFocus: onFocus,
onBlur: onBlur,
onMouseEnter,
onMouseLeave,
accessibilityLabel: label,
},
})
}
</RadixTriggerPassThrough>
</DropdownMenu.Trigger>
)
}
export function Outer({children}: React.PropsWithChildren<{}>) {
export function Outer({
children,
style,
}: React.PropsWithChildren<{
showCancel?: boolean
style?: StyleProp<ViewStyle>
}>) {
const t = useTheme()
return (
@ -125,6 +170,7 @@ export function Outer({children}: React.PropsWithChildren<{}>) {
a.p_xs,
t.name === 'light' ? t.atoms.bg : t.atoms.bg_contrast_25,
t.atoms.shadow_md,
style,
]}>
{children}
</View>

View file

@ -1,5 +1,9 @@
import React from 'react'
import {GestureResponderEvent, PressableProps} from 'react-native'
import {
GestureResponderEvent,
PressableProps,
AccessibilityProps,
} from 'react-native'
import {Props as SVGIconProps} from '#/components/icons/common'
import * as Dialog from '#/components/Dialog'
@ -9,7 +13,23 @@ export type ContextType = {
control: Dialog.DialogOuterProps['control']
}
export type TriggerProps = ViewStyleProp & {
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']
onKeyDown: (e: React.KeyboardEvent) => void
/**
* Radix provides this, but we override on web to use `onPress` instead,
* which is less sensitive while scrolling.
*/
onPointerDown: PressableProps['onPointerDown']
}
export type TriggerProps = {
children(props: TriggerChildProps): React.ReactNode
label: string
}
@ -52,7 +72,14 @@ export type TriggerChildProps =
*/
pressed: false
}
props: {}
props: RadixPassThroughTriggerProps & {
onPress: () => void
onFocus: () => void
onBlur: () => void
onMouseEnter: () => void
onMouseLeave: () => void
accessibilityLabel: string
}
}
export type ItemProps = React.PropsWithChildren<

View file

@ -1,11 +1,12 @@
import React from 'react'
import {View, PressableProps} from 'react-native'
import {View} from 'react-native'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {isNative} from '#/platform/detection'
import {useTheme, atoms as a, useBreakpoints} from '#/alf'
import {Text} from '#/components/Typography'
import {Button} from '#/components/Button'
import {Button, ButtonColor, ButtonText} from '#/components/Button'
import * as Dialog from '#/components/Dialog'
@ -22,8 +23,10 @@ const Context = React.createContext<{
export function Outer({
children,
control,
testID,
}: React.PropsWithChildren<{
control: Dialog.DialogOuterProps['control']
testID?: string
}>) {
const {gtMobile} = useBreakpoints()
const titleId = React.useId()
@ -35,7 +38,7 @@ export function Outer({
)
return (
<Dialog.Outer control={control}>
<Dialog.Outer control={control} testID={testID}>
<Context.Provider value={context}>
<Dialog.Handle />
@ -80,7 +83,10 @@ export function Actions({children}: React.PropsWithChildren<{}>) {
a.w_full,
a.gap_sm,
a.justify_end,
gtMobile ? [a.flex_row] : [a.flex_col, a.pt_md, a.pb_4xl],
gtMobile
? [a.flex_row, a.flex_row_reverse, a.justify_start]
: [a.flex_col],
isNative && [a.pb_4xl],
]}>
{children}
</View>
@ -89,18 +95,29 @@ export function Actions({children}: React.PropsWithChildren<{}>) {
export function Cancel({
children,
}: React.PropsWithChildren<{onPress?: PressableProps['onPress']}>) {
cta,
}: React.PropsWithChildren<{
/**
* Optional i18n string, used in lieu of `children` for simple buttons. If
* undefined (and `children` is undefined), it will default to "Cancel".
*/
cta?: string
}>) {
const {_} = useLingui()
const {gtMobile} = useBreakpoints()
const {close} = Dialog.useDialogContext()
const onPress = React.useCallback(() => {
close()
}, [close])
return (
<Button
variant="solid"
color="secondary"
size={gtMobile ? 'small' : 'medium'}
label={_(msg`Cancel`)}
onPress={() => close()}>
{children}
label={cta || _(msg`Cancel`)}
onPress={onPress}>
{children ? children : <ButtonText>{cta || _(msg`Cancel`)}</ButtonText>}
</Button>
)
}
@ -108,22 +125,70 @@ export function Cancel({
export function Action({
children,
onPress,
}: React.PropsWithChildren<{onPress?: () => void}>) {
color = 'primary',
cta,
testID,
}: React.PropsWithChildren<{
onPress: () => void
color?: ButtonColor
/**
* Optional i18n string, used in lieu of `children` for simple buttons. If
* undefined (and `children` is undefined), it will default to "Confirm".
*/
cta?: string
testID?: string
}>) {
const {_} = useLingui()
const {gtMobile} = useBreakpoints()
const {close} = Dialog.useDialogContext()
const handleOnPress = React.useCallback(() => {
close()
onPress?.()
onPress()
}, [close, onPress])
return (
<Button
variant="solid"
color="primary"
color={color}
size={gtMobile ? 'small' : 'medium'}
label={_(msg`Confirm`)}
onPress={handleOnPress}>
{children}
label={cta || _(msg`Confirm`)}
onPress={handleOnPress}
testID={testID}>
{children ? children : <ButtonText>{cta || _(msg`Confirm`)}</ButtonText>}
</Button>
)
}
export function Basic({
control,
title,
description,
cancelButtonCta,
confirmButtonCta,
onConfirm,
confirmButtonColor,
}: React.PropsWithChildren<{
control: Dialog.DialogOuterProps['control']
title: string
description: string
cancelButtonCta?: string
confirmButtonCta?: string
onConfirm: () => void
confirmButtonColor?: ButtonColor
}>) {
return (
<Outer control={control} testID="confirmModal">
<Title>{title}</Title>
<Description>{description}</Description>
<Actions>
<Action
cta={confirmButtonCta}
onPress={onConfirm}
color={confirmButtonColor}
testID="confirmBtn"
/>
<Cancel cta={cancelButtonCta} />
</Actions>
</Outer>
)
}

View file

@ -254,9 +254,9 @@ function MutedWordsInner({}: {control: Dialog.DialogOuterProps['control']}) {
</View>
{isNative && <View style={{height: 20}} />}
<Dialog.Close />
</View>
<Dialog.Close />
</Dialog.ScrollableInner>
)
}
@ -277,29 +277,16 @@ function MutedWordRow({
return (
<>
<Prompt.Outer control={control}>
<Prompt.Title>
<Trans>Are you sure?</Trans>
</Prompt.Title>
<Prompt.Description>
<Trans>
This will delete {word.value} from your muted words. You can always
add it back later.
</Trans>
</Prompt.Description>
<Prompt.Actions>
<Prompt.Cancel>
<ButtonText>
<Trans>Nevermind</Trans>
</ButtonText>
</Prompt.Cancel>
<Prompt.Action onPress={remove}>
<ButtonText>
<Trans>Remove</Trans>
</ButtonText>
</Prompt.Action>
</Prompt.Actions>
</Prompt.Outer>
<Prompt.Basic
control={control}
title={_(msg`Are you sure?`)}
description={_(
msg`This will delete ${word.value} from your muted words. You can always add it back later.`,
)}
onConfirm={remove}
confirmButtonCta={_(msg`Remove`)}
confirmButtonColor="negative"
/>
<View
style={[

View file

@ -0,0 +1,9 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Camera_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M8.371 3.89A2 2 0 0 1 10.035 3h3.93a2 2 0 0 1 1.664.89L17.035 6H20a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2.965L8.37 3.89ZM13.965 5h-3.93L8.63 7.11A2 2 0 0 1 6.965 8H4v11h16V8h-2.965a2 2 0 0 1-1.664-.89L13.965 5ZM12 11a2 2 0 1 0 0 4 2 2 0 0 0 0-4Zm-4 2a4 4 0 1 1 8 0 4 4 0 0 1-8 0Z',
})
export const Camera_Filled_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M8.371 3.89A2 2 0 0 1 10.035 3h3.93a2 2 0 0 1 1.664.89L17.035 6H20a2 2 0 0 1 2 2v11a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h2.965L8.37 3.89ZM12 9a3.5 3.5 0 1 0 0 7 3.5 3.5 0 0 0 0-7Z',
})

View file

@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const DotGrid_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M2 12a2 2 0 1 1 4 0 2 2 0 0 1-4 0Zm16 0a2 2 0 1 1 4 0 2 2 0 0 1-4 0Zm-6-2a2 2 0 1 0 0 4 2 2 0 0 0 0-4Z',
})

View file

@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Flag_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M4 4a2 2 0 0 1 2-2h13.131c1.598 0 2.55 1.78 1.665 3.11L18.202 9l2.594 3.89c.886 1.33-.067 3.11-1.665 3.11H6v5a1 1 0 1 1-2 0V4Zm2 10h13.131l-2.593-3.89a2 2 0 0 1 0-2.22L19.13 4H6v10Z',
})

View file

@ -0,0 +1,9 @@
import {createSinglePathSVG} from './TEMPLATE'
export const Heart2_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M16.734 5.091c-1.238-.276-2.708.047-4.022 1.38a1 1 0 0 1-1.424 0C9.974 5.137 8.504 4.814 7.266 5.09c-1.263.282-2.379 1.206-2.92 2.556C3.33 10.18 4.252 14.84 12 19.348c7.747-4.508 8.67-9.168 7.654-11.7-.541-1.351-1.657-2.275-2.92-2.557Zm4.777 1.812c1.604 4-.494 9.69-9.022 14.47a1 1 0 0 1-.978 0C2.983 16.592.885 10.902 2.49 6.902c.779-1.942 2.414-3.334 4.342-3.764 1.697-.378 3.552.003 5.169 1.286 1.617-1.283 3.472-1.664 5.17-1.286 1.927.43 3.562 1.822 4.34 3.764Z',
})
export const Heart2_Filled_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M12.489 21.372c8.528-4.78 10.626-10.47 9.022-14.47-.779-1.941-2.414-3.333-4.342-3.763-1.697-.378-3.552.003-5.169 1.287-1.617-1.284-3.472-1.665-5.17-1.287-1.927.43-3.562 1.822-4.34 3.764-1.605 4 .493 9.69 9.021 14.47a1 1 0 0 0 .978 0Z',
})

View file

@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const PeopleRemove2_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M10 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM5.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM16 11a1 1 0 0 1 1-1h5a1 1 0 1 1 0 2h-5a1 1 0 0 1-1-1ZM3.678 19h12.644c-.71-2.909-3.092-5-6.322-5s-5.613 2.091-6.322 5Zm-2.174.906C1.917 15.521 5.242 12 10 12c4.758 0 8.083 3.521 8.496 7.906A1 1 0 0 1 17.5 21h-15a1 1 0 0 1-.996-1.094Z',
})

View file

@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const PersonCheck_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.679 19c.709-2.902 3.079-5 6.321-5a6.69 6.69 0 0 1 2.612.51 1 1 0 0 0 .776-1.844A8.687 8.687 0 0 0 12 12c-4.3 0-7.447 2.884-8.304 6.696-.29 1.29.767 2.304 1.902 2.304H11a1 1 0 1 0 0-2H5.679Zm14.835-4.857a1 1 0 0 1 .344 1.371l-3 5a1 1 0 0 1-1.458.286l-2-1.5a1 1 0 0 1 1.2-1.6l1.113.835 2.43-4.05a1 1 0 0 1 1.372-.342Z',
})

View file

@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const PersonX_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M12 4a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5ZM7.5 6.5a4.5 4.5 0 1 1 9 0 4.5 4.5 0 0 1-9 0ZM5.679 19c.709-2.902 3.079-5 6.321-5 .302 0 .595.018.878.053a1 1 0 0 0 .243-1.985A9.235 9.235 0 0 0 12 12c-4.3 0-7.447 2.884-8.304 6.696-.29 1.29.767 2.304 1.902 2.304H12a1 1 0 1 0 0-2H5.679Zm9.614-3.707a1 1 0 0 1 1.414 0L18 16.586l1.293-1.293a1 1 0 0 1 1.414 1.414L19.414 18l1.293 1.293a1 1 0 0 1-1.414 1.414L18 19.414l-1.293 1.293a1 1 0 0 1-1.414-1.414L16.586 18l-1.293-1.293a1 1 0 0 1 0-1.414Z',
})

View file

@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const StreamingLive_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M4 4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2H4Zm8 12.5c1.253 0 2.197.609 2.674 1.5H9.326c.477-.891 1.42-1.5 2.674-1.5Zm0-2c2.404 0 4.235 1.475 4.822 3.5H20V6H4v12h3.178c.587-2.025 2.418-3.5 4.822-3.5Zm-1.25-3.75a1.25 1.25 0 1 1 2.5 0 1.25 1.25 0 0 1-2.5 0ZM12 7.5a3.25 3.25 0 1 0 0 6.5 3.25 3.25 0 0 0 0-6.5Zm5.75 2a1.25 1.25 0 1 0 0-2.5 1.25 1.25 0 0 0 0 2.5Z',
})