Merge branch 'hailey/dialogs-pt7-rework' into hailey/dialogs-pt8

This commit is contained in:
Hailey
2024-10-02 16:58:37 -07:00
11 changed files with 388 additions and 373 deletions
+6 -104
View File
@@ -1,110 +1,12 @@
import React from 'react' import React from 'react'
import {AccessibilityProps, ViewStyle} from 'react-native'
import {PressableEvent} from 'react-native-gesture-handler/lib/typescript/components/Pressable/PressableProps'
import {
BlueskyBottomSheetPressable,
BlueskyBottomSheetPressableProps,
} from '@haileyok/bluesky-bottom-sheet'
import {atoms as a} from '#/alf' import {Button, ButtonProps} from '#/components/Button'
import { import {NormalizedRNGHPressable} from '#/components/NormalizedRNGHPressable'
ButtonContext,
useSharedButtonStyles,
VariantProps,
} from '#/components/Button'
export function BottomSheetButton({
children,
label,
color,
variant,
shape = 'default',
size,
disabled,
style,
...rest
}: BlueskyBottomSheetPressableProps &
AccessibilityProps &
VariantProps & {
/**
* For a11y, try to make this descriptive and clear
*/
label: string
}) {
const [state, setState] = React.useState({
pressed: false,
hovered: false,
focused: false,
})
const onPressInOuter = rest.onPressIn
const onPressIn = React.useCallback(
(e: PressableEvent) => {
setState(s => ({
...s,
pressed: true,
}))
onPressInOuter?.(e)
},
[setState, onPressInOuter],
)
const onPressOutOuter = rest.onPressOut
const onPressOut = React.useCallback(
(e: PressableEvent) => {
setState(s => ({
...s,
pressed: false,
}))
onPressOutOuter?.(e)
},
[setState, onPressOutOuter],
)
const {baseStyles, hoverStyles} = useSharedButtonStyles({
/**
* For a11y, try to make this descriptive and clear
*/
color,
variant,
shape,
size,
disabled,
})
const context = React.useMemo<ButtonContext>(
() => ({
...state,
variant,
color,
size,
disabled: disabled || false,
}),
[state, variant, color, size, disabled],
)
export function BottomSheetButton({children, ...rest}: ButtonProps) {
return ( return (
<BlueskyBottomSheetPressable <Button {...rest} Component={NormalizedRNGHPressable}>
style={[ {children}
a.flex_row, </Button>
a.align_center,
a.justify_center,
baseStyles,
style as ViewStyle,
...(state.hovered || state.pressed ? [hoverStyles] : []),
]}
aria-label={label}
aria-pressed={state.pressed}
accessibilityLabel={label}
accessibilityHint={undefined}
accessibilityState={{
disabled: disabled || false,
}}
onPressIn={onPressIn}
onPressOut={onPressOut}
{...rest}>
<ButtonContext.Provider value={context}>
{typeof children === 'function' ? children(context) : children}
</ButtonContext.Provider>
</BlueskyBottomSheetPressable>
) )
} }
+3 -3
View File
@@ -1,5 +1,4 @@
import React from 'react' import React from 'react'
import {BlueskyBottomSheetPressable} from '@haileyok/bluesky-bottom-sheet'
import {StackActions, useNavigation} from '@react-navigation/native' import {StackActions, useNavigation} from '@react-navigation/native'
import {NavigationProp} from '#/lib/routes/types' import {NavigationProp} from '#/lib/routes/types'
@@ -7,6 +6,7 @@ import {flatten, useTheme} from '#/alf'
import {useDialogContext} from '#/components/Dialog' import {useDialogContext} from '#/components/Dialog'
import {useInteractionState} from '#/components/hooks/useInteractionState' import {useInteractionState} from '#/components/hooks/useInteractionState'
import {InlineLinkProps, useLink} from '#/components/Link' import {InlineLinkProps, useLink} from '#/components/Link'
import {NormalizedRNGHPressable} from '#/components/NormalizedRNGHPressable'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
import {router} from '#/routes' import {router} from '#/routes'
@@ -64,7 +64,7 @@ export function BottomSheetInlineLinkText({
// eslint-disable-next-line bsky-internal/avoid-unwrapped-text // eslint-disable-next-line bsky-internal/avoid-unwrapped-text
return ( return (
<BlueskyBottomSheetPressable <NormalizedRNGHPressable
onPress={onPress} onPress={onPress}
onLongPress={onLongPress} onLongPress={onLongPress}
onPressIn={onPressIn} onPressIn={onPressIn}
@@ -87,6 +87,6 @@ export function BottomSheetInlineLinkText({
]}> ]}>
{children} {children}
</Text> </Text>
</BlueskyBottomSheetPressable> </NormalizedRNGHPressable>
) )
} }
+120 -125
View File
@@ -15,7 +15,9 @@ import {
import {LinearGradient} from 'expo-linear-gradient' import {LinearGradient} from 'expo-linear-gradient'
import {atoms as a, flatten, select, tokens, useTheme, web} from '#/alf' import {atoms as a, flatten, select, tokens, useTheme, web} from '#/alf'
import {useDialogContext} from '#/components/Dialog'
import {Props as SVGIconProps} from '#/components/icons/common' import {Props as SVGIconProps} from '#/components/icons/common'
import {NormalizedRNGHPressable} from '#/components/NormalizedRNGHPressable'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient' export type ButtonVariant = 'solid' | 'outline' | 'ghost' | 'gradient'
@@ -87,10 +89,10 @@ export type ButtonProps = Pick<
style?: StyleProp<ViewStyle> style?: StyleProp<ViewStyle>
hoverStyle?: StyleProp<ViewStyle> hoverStyle?: StyleProp<ViewStyle>
children: NonTextElements | ((context: ButtonContext) => NonTextElements) children: NonTextElements | ((context: ButtonContext) => NonTextElements)
Component?: React.ComponentType<PressableProps>
} }
export type ButtonTextProps = TextProps & export type ButtonTextProps = TextProps & VariantProps & {disabled?: boolean}
VariantProps & {disabled?: boolean | null}
const Context = React.createContext<VariantProps & ButtonState>({ const Context = React.createContext<VariantProps & ButtonState>({
hovered: false, hovered: false,
@@ -98,7 +100,6 @@ const Context = React.createContext<VariantProps & ButtonState>({
pressed: false, pressed: false,
disabled: false, disabled: false,
}) })
export const ButtonContext = Context
export function useButtonContext() { export function useButtonContext() {
return React.useContext(Context) return React.useContext(Context)
@@ -116,10 +117,23 @@ export const Button = React.forwardRef<View, ButtonProps>(
disabled = false, disabled = false,
style, style,
hoverStyle: hoverStyleProp, hoverStyle: hoverStyleProp,
Component,
...rest ...rest
}, },
ref, ref,
) => { ) => {
// This will pick the correct default pressable to use. If we are inside a dialog, we need to use the RNGH
// pressable so that it is usable inside the dialog.
const {insideDialog} = useDialogContext()
if (!Component) {
if (insideDialog) {
Component = NormalizedRNGHPressable
} else {
Component = Pressable
}
}
const t = useTheme()
const [state, setState] = React.useState({ const [state, setState] = React.useState({
pressed: false, pressed: false,
hovered: false, hovered: false,
@@ -183,126 +197,6 @@ export const Button = React.forwardRef<View, ButtonProps>(
})) }))
}, [setState]) }, [setState])
const {baseStyles, hoverStyles} = useSharedButtonStyles({
color,
variant,
disabled,
shape,
size,
})
const {gradientColors, gradientHoverColors, gradientLocations} =
React.useMemo(() => {
const colors: string[] = []
const hoverColors: string[] = []
const locations: number[] = []
const gradient = {
primary: tokens.gradients.sky,
secondary: tokens.gradients.sky,
secondary_inverted: tokens.gradients.sky,
negative: tokens.gradients.sky,
gradient_primary: tokens.gradients.primary,
gradient_sky: tokens.gradients.sky,
gradient_midnight: tokens.gradients.midnight,
gradient_sunrise: tokens.gradients.sunrise,
gradient_sunset: tokens.gradients.sunset,
gradient_nordic: tokens.gradients.nordic,
gradient_bonfire: tokens.gradients.bonfire,
}[color || 'primary']
if (variant === 'gradient') {
colors.push(...gradient.values.map(([_, color]) => color))
hoverColors.push(...gradient.values.map(_ => gradient.hover_value))
locations.push(...gradient.values.map(([location, _]) => location))
}
return {
gradientColors: colors,
gradientHoverColors: hoverColors,
gradientLocations: locations,
}
}, [variant, color])
const context = React.useMemo<ButtonContext>(
() => ({
...state,
variant,
color,
size,
disabled: disabled || false,
}),
[state, variant, color, size, disabled],
)
const flattenedBaseStyles = flatten([baseStyles, style])
return (
<Pressable
role="button"
accessibilityHint={undefined} // optional
{...rest}
ref={ref}
aria-label={label}
aria-pressed={state.pressed}
accessibilityLabel={label}
disabled={disabled || false}
accessibilityState={{
disabled: disabled || false,
}}
style={[
a.flex_row,
a.align_center,
a.justify_center,
flattenedBaseStyles,
...(state.hovered || state.pressed
? [hoverStyles, flatten(hoverStyleProp)]
: []),
]}
onPressIn={onPressIn}
onPressOut={onPressOut}
onHoverIn={onHoverIn}
onHoverOut={onHoverOut}
onFocus={onFocus}
onBlur={onBlur}>
{variant === 'gradient' && (
<View
style={[
a.absolute,
a.inset_0,
a.overflow_hidden,
{borderRadius: flattenedBaseStyles.borderRadius},
]}>
<LinearGradient
colors={
state.hovered || state.pressed
? gradientHoverColors
: gradientColors
}
locations={gradientLocations}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[a.absolute, a.inset_0]}
/>
</View>
)}
<Context.Provider value={context}>
{typeof children === 'function' ? children(context) : children}
</Context.Provider>
</Pressable>
)
},
)
Button.displayName = 'Button'
export function useSharedButtonStyles({
color,
variant,
disabled,
shape,
size,
}: VariantProps & {disabled?: boolean | null}) {
const t = useTheme()
const {baseStyles, hoverStyles} = React.useMemo(() => { const {baseStyles, hoverStyles} = React.useMemo(() => {
const baseStyles: ViewStyle[] = [] const baseStyles: ViewStyle[] = []
const hoverStyles: ViewStyle[] = [] const hoverStyles: ViewStyle[] = []
@@ -524,8 +418,109 @@ export function useSharedButtonStyles({
} }
}, [t, variant, color, size, shape, disabled]) }, [t, variant, color, size, shape, disabled])
return {baseStyles, hoverStyles} const {gradientColors, gradientHoverColors, gradientLocations} =
} React.useMemo(() => {
const colors: string[] = []
const hoverColors: string[] = []
const locations: number[] = []
const gradient = {
primary: tokens.gradients.sky,
secondary: tokens.gradients.sky,
secondary_inverted: tokens.gradients.sky,
negative: tokens.gradients.sky,
gradient_primary: tokens.gradients.primary,
gradient_sky: tokens.gradients.sky,
gradient_midnight: tokens.gradients.midnight,
gradient_sunrise: tokens.gradients.sunrise,
gradient_sunset: tokens.gradients.sunset,
gradient_nordic: tokens.gradients.nordic,
gradient_bonfire: tokens.gradients.bonfire,
}[color || 'primary']
if (variant === 'gradient') {
colors.push(...gradient.values.map(([_, color]) => color))
hoverColors.push(...gradient.values.map(_ => gradient.hover_value))
locations.push(...gradient.values.map(([location, _]) => location))
}
return {
gradientColors: colors,
gradientHoverColors: hoverColors,
gradientLocations: locations,
}
}, [variant, color])
const context = React.useMemo<ButtonContext>(
() => ({
...state,
variant,
color,
size,
disabled: disabled || false,
}),
[state, variant, color, size, disabled],
)
const flattenedBaseStyles = flatten([baseStyles, style])
return (
<Component
role="button"
accessibilityHint={undefined} // optional
{...rest}
// @ts-expect-error ref type
ref={ref}
aria-label={label}
aria-pressed={state.pressed}
accessibilityLabel={label}
disabled={disabled || false}
accessibilityState={{
disabled: disabled || false,
}}
style={[
a.flex_row,
a.align_center,
a.justify_center,
flattenedBaseStyles,
...(state.hovered || state.pressed
? [hoverStyles, flatten(hoverStyleProp)]
: []),
]}
onPressIn={onPressIn}
onPressOut={onPressOut}
onHoverIn={onHoverIn}
onHoverOut={onHoverOut}
onFocus={onFocus}
onBlur={onBlur}>
{variant === 'gradient' && (
<View
style={[
a.absolute,
a.inset_0,
a.overflow_hidden,
{borderRadius: flattenedBaseStyles.borderRadius},
]}>
<LinearGradient
colors={
state.hovered || state.pressed
? gradientHoverColors
: gradientColors
}
locations={gradientLocations}
start={{x: 0, y: 0}}
end={{x: 1, y: 1}}
style={[a.absolute, a.inset_0]}
/>
</View>
)}
<Context.Provider value={context}>
{typeof children === 'function' ? children(context) : children}
</Context.Provider>
</Component>
)
},
)
Button.displayName = 'Button'
export function useSharedButtonTextStyles() { export function useSharedButtonTextStyles() {
const t = useTheme() const t = useTheme()
+1
View File
@@ -9,6 +9,7 @@ import {
export const Context = React.createContext<DialogContextProps>({ export const Context = React.createContext<DialogContextProps>({
close: () => {}, close: () => {},
insideDialog: false,
}) })
export function useDialogContext() { export function useDialogContext() {
+1 -10
View File
@@ -56,7 +56,6 @@ export function OuterWithoutPortal({
const insets = useSafeAreaInsets() const insets = useSafeAreaInsets()
const closeCallbacks = React.useRef<(() => void)[]>([]) const closeCallbacks = React.useRef<(() => void)[]>([])
const {setDialogIsOpen} = useDialogStateControlContext() const {setDialogIsOpen} = useDialogStateControlContext()
// @TODO DIALOG REFACTOR - can i get rid of this? seems pointless tbh
const callQueuedCallbacks = React.useCallback(() => { const callQueuedCallbacks = React.useCallback(() => {
for (const cb of closeCallbacks.current) { for (const cb of closeCallbacks.current) {
@@ -104,21 +103,13 @@ export function OuterWithoutPortal({
[open, close], [open, close],
) )
// @TODO DIALOG REFACTOR - what is this? rm i think? const context = React.useMemo(() => ({close, insideDialog: true}), [close])
// React.useEffect(() => {
// return () => {
// setDialogIsOpen(control.id, false)
// }
// }, [control.id, setDialogIsOpen])
const context = React.useMemo(() => ({close}), [close])
const Wrapper = isIOS ? View : GestureHandlerRootView const Wrapper = isIOS ? View : GestureHandlerRootView
return ( return (
<Context.Provider value={context}> <Context.Provider value={context}>
<BlueskyBottomSheetView <BlueskyBottomSheetView
// handleIndicatorStyle={{backgroundColor: t.palette.primary_500}} // @TODO DIALOG REFACTOR need to add this to lib!*/
ref={ref} ref={ref}
topInset={30} topInset={30}
bottomInset={insets.bottom} bottomInset={insets.bottom}
-4
View File
@@ -229,10 +229,6 @@ export const InnerFlatList = React.forwardRef<
) )
}) })
export function Handle() {
return null
}
export function Close() { export function Close() {
const {_} = useLingui() const {_} = useLingui()
const {close} = React.useContext(Context) const {close} = React.useContext(Context)
+1
View File
@@ -37,6 +37,7 @@ export type DialogControlProps = DialogControlRefProps & {
export type DialogContextProps = { export type DialogContextProps = {
close: DialogControlProps['close'] close: DialogControlProps['close']
insideDialog: boolean
} }
export type DialogControlOpenOptions = { export type DialogControlOpenOptions = {
+3 -3
View File
@@ -1,6 +1,5 @@
import React from 'react' import React from 'react'
import {StyleProp, View, ViewStyle} from 'react-native' import {StyleProp, View, ViewStyle} from 'react-native'
import {BlueskyBottomSheetPressable as Pressable} from '@haileyok/bluesky-bottom-sheet'
import {msg, Trans} from '@lingui/macro' import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react' import {useLingui} from '@lingui/react'
import flattenReactChildren from 'react-keyed-flatten-children' import flattenReactChildren from 'react-keyed-flatten-children'
@@ -19,6 +18,7 @@ import {
ItemTextProps, ItemTextProps,
TriggerProps, TriggerProps,
} from '#/components/Menu/types' } from '#/components/Menu/types'
import {NormalizedRNGHPressable} from '#/components/NormalizedRNGHPressable'
import {Text} from '#/components/Typography' import {Text} from '#/components/Typography'
export { export {
@@ -117,7 +117,7 @@ export function Item({children, label, style, onPress, ...rest}: ItemProps) {
} = useInteractionState() } = useInteractionState()
return ( return (
<Pressable <NormalizedRNGHPressable
{...rest} {...rest}
accessibilityHint="" accessibilityHint=""
accessibilityLabel={label} accessibilityLabel={label}
@@ -151,7 +151,7 @@ export function Item({children, label, style, onPress, ...rest}: ItemProps) {
<ItemContext.Provider value={{disabled: Boolean(rest.disabled)}}> <ItemContext.Provider value={{disabled: Boolean(rest.disabled)}}>
{children} {children}
</ItemContext.Provider> </ItemContext.Provider>
</Pressable> </NormalizedRNGHPressable>
) )
} }
+2 -4
View File
@@ -4,8 +4,6 @@ import {
GestureResponderEvent, GestureResponderEvent,
PressableProps, PressableProps,
} from 'react-native' } from 'react-native'
import {PressableEvent} from 'react-native-gesture-handler/lib/typescript/components/Pressable/PressableProps'
import {BlueskyBottomSheetPressableProps} from '@haileyok/bluesky-bottom-sheet'
import {TextStyleProp, ViewStyleProp} from '#/alf' import {TextStyleProp, ViewStyleProp} from '#/alf'
import * as Dialog from '#/components/Dialog' import * as Dialog from '#/components/Dialog'
@@ -89,10 +87,10 @@ export type TriggerChildProps =
} }
export type ItemProps = React.PropsWithChildren< export type ItemProps = React.PropsWithChildren<
Omit<BlueskyBottomSheetPressableProps, 'style'> & Omit<PressableProps, 'style'> &
ViewStyleProp & { ViewStyleProp & {
label: string label: string
onPress: (e: PressableEvent | GestureResponderEvent) => void onPress: (e: GestureResponderEvent) => void
} }
> >
+128
View File
@@ -0,0 +1,128 @@
import React from 'react'
import {
GestureResponderEvent,
MeasureOnSuccessCallback,
NativeMouseEvent,
NativeSyntheticEvent,
PressableProps,
} from 'react-native'
import {Pressable as BSPressable} from 'react-native-gesture-handler'
import {PressableEvent} from 'react-native-gesture-handler/lib/typescript/components/Pressable/PressableProps'
function pressableEventToGestureResponderEvent(
event: PressableEvent,
target: NormalizedRNGHPressable,
): GestureResponderEvent {
return {
nativeEvent: {
...event.nativeEvent,
touches: [],
changedTouches: [],
identifier: event.nativeEvent.identifier.toString(),
target: event.nativeEvent.target.toString(),
},
// @ts-expect-error
target: target,
// @ts-expect-error
currentTarget: target,
preventDefault() {},
stopPropagation() {},
cancelable: false,
defaultPrevented: false,
eventPhase: 0,
isTrusted: false,
bubbles: false,
timeStamp: event.nativeEvent.timestamp,
isDefaultPrevented(): boolean {
return false
},
isPropagationStopped(): boolean {
return false
},
persist() {},
type: 'press',
}
}
function pressableEventToMouseEvent(
event: PressableEvent,
target: NormalizedRNGHPressable,
): MouseEvent & NativeSyntheticEvent<NativeMouseEvent> {
return {
...event.nativeEvent,
// @ts-expect-error
target: target,
// @ts-expect-error
currentTarget: target,
preventDefault() {},
stopPropagation() {},
cancelable: false,
defaultPrevented: false,
eventPhase: 0,
isTrusted: false,
bubbles: false,
timeStamp: event.nativeEvent.timestamp,
}
}
export class NormalizedRNGHPressable extends React.Component<PressableProps> {
static displayName = 'Pressable'
measure = (_: MeasureOnSuccessCallback) => {}
measureLayout = (_: number) => {}
measureInWindow = (
_: (x: number, y: number, width: number, height: number) => void,
) => {}
setNativeProps = (_: PressableProps) => {}
focus = () => {}
blur = () => {}
onPress = (event: PressableEvent) => {
if (!this.props.onPress) return
this.props.onPress(pressableEventToGestureResponderEvent(event, this))
}
onLongPress = (event: PressableEvent) => {
if (!this.props.onLongPress) return
this.props.onLongPress(pressableEventToGestureResponderEvent(event, this))
}
onPressIn = (event: PressableEvent) => {
if (!this.props.onPressIn) return
this.props.onPressIn(pressableEventToGestureResponderEvent(event, this))
}
onPressOut = (event: PressableEvent) => {
if (!this.props.onPressOut) return
this.props.onPressOut(pressableEventToGestureResponderEvent(event, this))
}
onHoverIn = (event: PressableEvent) => {
if (!this.props.onHoverIn) return
this.props.onHoverIn(pressableEventToMouseEvent(event, this))
}
onHoverOut = (event: PressableEvent) => {
if (!this.props.onHoverOut) return
this.props.onHoverOut(pressableEventToMouseEvent(event, this))
}
render() {
return (
<BSPressable
{...this.props}
onPress={this.onPress}
onLongPress={this.onLongPress}
onPressIn={this.onPressIn}
onPressOut={this.onPressOut}
onHoverIn={this.onHoverIn}
onHoverOut={this.onHoverOut}
/>
)
}
}
@@ -0,0 +1,3 @@
import {Pressable as NormalizedRNGHPressable} from 'react-native'
export {NormalizedRNGHPressable}