Improve dialogs (#2933)

* Improve dialogs

* Remove comment, revert storybook

* Hacky fix

* Comments
zio/stable
Eric Bailey 2024-02-19 18:18:13 -06:00 committed by GitHub
parent da62a77f05
commit b52a742925
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 123 additions and 85 deletions

View File

@ -8,7 +8,7 @@ import BottomSheet, {
} from '@gorhom/bottom-sheet'
import {useSafeAreaInsets} from 'react-native-safe-area-context'
import {useTheme, atoms as a} from '#/alf'
import {useTheme, atoms as a, flatten} from '#/alf'
import {Portal} from '#/components/Portal'
import {createInput} from '#/components/forms/TextField'
@ -36,9 +36,23 @@ export function Outer({
const hasSnapPoints = !!sheetOptions.snapPoints
const insets = useSafeAreaInsets()
const open = React.useCallback<DialogControlProps['open']>((i = 0) => {
sheet.current?.snapToIndex(i)
}, [])
/*
* Used to manage open/closed, but index is otherwise handled internally by `BottomSheet`
*/
const [openIndex, setOpenIndex] = React.useState(-1)
/*
* `openIndex` is the index of the snap point to open the bottom sheet to. If >0, the bottom sheet is open.
*/
const isOpen = openIndex > -1
const open = React.useCallback<DialogControlProps['open']>(
({index} = {}) => {
// can be set to any index of `snapPoints`, but `0` is the first i.e. "open"
setOpenIndex(index || 0)
},
[setOpenIndex],
)
const close = React.useCallback(() => {
sheet.current?.close()
@ -57,14 +71,16 @@ export function Outer({
(index: number) => {
if (index === -1) {
onClose?.()
setOpenIndex(-1)
}
},
[onClose],
[onClose, setOpenIndex],
)
const context = React.useMemo(() => ({close}), [close])
return (
isOpen && (
<Portal>
<BottomSheet
enableDynamicSizing={!hasSnapPoints}
@ -75,7 +91,7 @@ export function Outer({
topInset={insets.top}
{...sheetOptions}
ref={sheet}
index={-1}
index={openIndex}
backgroundStyle={{backgroundColor: 'transparent'}}
backdropComponent={props => (
<BottomSheetBackdrop
@ -101,33 +117,34 @@ export function Outer({
},
]}
/>
{children}
{hasSnapPoints ? children : <View>{children}</View>}
</Context.Provider>
</BottomSheet>
</Portal>
)
)
}
// TODO a11y props here, or is that handled by the sheet?
export function Inner(props: DialogInnerProps) {
export function Inner({children, style}: DialogInnerProps) {
const insets = useSafeAreaInsets()
return (
<BottomSheetView
style={[
a.p_lg,
a.p_xl,
{
paddingTop: 40,
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
paddingBottom: insets.bottom + a.pb_5xl.paddingBottom,
},
flatten(style),
]}>
{props.children}
{children}
</BottomSheetView>
)
}
export function ScrollableInner(props: DialogInnerProps) {
export function ScrollableInner({children, style}: DialogInnerProps) {
const insets = useSafeAreaInsets()
return (
<BottomSheetScrollView
@ -136,13 +153,15 @@ export function ScrollableInner(props: DialogInnerProps) {
style={[
a.flex_1, // main diff is this
a.p_xl,
a.h_full,
{
paddingTop: 40,
borderTopLeftRadius: 40,
borderTopRightRadius: 40,
},
flatten(style),
]}>
{props.children}
{children}
<View style={{height: insets.bottom + a.pt_5xl.paddingTop}} />
</BottomSheetScrollView>
)

View File

@ -5,11 +5,13 @@ import Animated, {FadeInDown, FadeIn} from 'react-native-reanimated'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
import {useTheme, atoms as a, useBreakpoints, web} from '#/alf'
import {useTheme, atoms as a, useBreakpoints, web, flatten} from '#/alf'
import {Portal} from '#/components/Portal'
import {DialogOuterProps, DialogInnerProps} from '#/components/Dialog/types'
import {Context} from '#/components/Dialog/context'
import {Button, ButtonIcon} from '#/components/Button'
import {TimesLarge_Stroke2_Corner0_Rounded as X} from '#/components/icons/Times'
export {useDialogControl, useDialogContext} from '#/components/Dialog/context'
export * from '#/components/Dialog/types'
@ -18,9 +20,9 @@ export {Input} from '#/components/forms/TextField'
const stopPropagation = (e: any) => e.stopPropagation()
export function Outer({
children,
control,
onClose,
children,
}: React.PropsWithChildren<DialogOuterProps>) {
const {_} = useLingui()
const t = useTheme()
@ -147,7 +149,7 @@ export function Inner({
a.rounded_md,
a.w_full,
a.border,
gtMobile ? a.p_xl : a.p_lg,
gtMobile ? a.p_2xl : a.p_xl,
t.atoms.bg,
{
maxWidth: 600,
@ -156,7 +158,7 @@ export function Inner({
shadowOpacity: t.name === 'light' ? 0.1 : 0.4,
shadowRadius: 30,
},
...(Array.isArray(style) ? style : [style || {}]),
flatten(style),
]}>
{children}
</Animated.View>
@ -170,25 +172,28 @@ export function Handle() {
return null
}
/**
* TODO(eric) unused rn
*/
// export function Close() {
// const {_} = useLingui()
// const t = useTheme()
// const {close} = useDialogContext()
// return (
// <View
// style={[
// a.absolute,
// a.z_10,
// {
// top: a.pt_lg.paddingTop,
// right: a.pr_lg.paddingRight,
// },
// ]}>
// <Button onPress={close} label={_(msg`Close active dialog`)}>
// </Button>
// </View>
// )
// }
export function Close() {
const {_} = useLingui()
const {close} = React.useContext(Context)
return (
<View
style={[
a.absolute,
a.z_10,
{
top: a.pt_md.paddingTop,
right: a.pr_md.paddingRight,
},
]}>
<Button
size="small"
variant="ghost"
color="primary"
shape="round"
onPress={close}
label={_(msg`Close active dialog`)}>
<ButtonIcon icon={X} size="md" />
</Button>
</View>
)
}

View File

@ -1,15 +1,27 @@
import React from 'react'
import type {ViewStyle, AccessibilityProps} from 'react-native'
import type {AccessibilityProps} from 'react-native'
import {BottomSheetProps} from '@gorhom/bottom-sheet'
import {ViewStyleProp} from '#/alf'
type A11yProps = Required<AccessibilityProps>
export type DialogContextProps = {
close: () => void
}
export type DialogControlOpenOptions = {
/**
* NATIVE ONLY
*
* Optional index of the snap point to open the bottom sheet to. Defaults to
* 0, which is the first snap point (i.e. "open").
*/
index?: number
}
export type DialogControlProps = {
open: (index?: number) => void
open: (options?: DialogControlOpenOptions) => void
close: () => void
}
@ -26,10 +38,7 @@ export type DialogOuterProps = {
webOptions?: {}
}
type DialogInnerPropsBase<T> = React.PropsWithChildren<{
style?: ViewStyle
}> &
T
type DialogInnerPropsBase<T> = React.PropsWithChildren<ViewStyleProp> & T
export type DialogInnerProps =
| DialogInnerPropsBase<{
label?: undefined

View File

@ -41,7 +41,7 @@ export function Outer({
<Dialog.Inner
accessibilityLabelledBy={titleId}
accessibilityDescribedBy={descriptionId}
style={{width: 'auto', maxWidth: 400}}>
style={[{width: 'auto', maxWidth: 400}]}>
{children}
</Dialog.Inner>
</Context.Provider>

View File

@ -0,0 +1,5 @@
import {createSinglePathSVG} from './TEMPLATE'
export const TimesLarge_Stroke2_Corner0_Rounded = createSinglePathSVG({
path: 'M4.293 4.293a1 1 0 0 1 1.414 0L12 10.586l6.293-6.293a1 1 0 1 1 1.414 1.414L13.414 12l6.293 6.293a1 1 0 0 1-1.414 1.414L12 13.414l-6.293 6.293a1 1 0 0 1-1.414-1.414L10.586 12 4.293 5.707a1 1 0 0 1 0-1.414Z',
})

View File

@ -50,7 +50,7 @@ export function Dialogs() {
<Dialog.Outer
control={control}
nativeOptions={{sheet: {snapPoints: ['90%']}}}>
nativeOptions={{sheet: {snapPoints: ['100%']}}}>
<Dialog.Handle />
<Dialog.ScrollableInner