add detached variant to modal
This commit is contained in:
@@ -23,6 +23,7 @@ export const Context = createContext<DialogContextProps>({
|
||||
disableDrag: false,
|
||||
setDisableDrag: () => {},
|
||||
isWithinDialog: false,
|
||||
type: 'sheet',
|
||||
})
|
||||
Context.displayName = 'DialogContext'
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, {useImperativeHandle} from 'react'
|
||||
import {
|
||||
type LayoutChangeEvent,
|
||||
Modal,
|
||||
type NativeScrollEvent,
|
||||
type NativeSyntheticEvent,
|
||||
Pressable,
|
||||
@@ -49,7 +50,112 @@ export * from '#/components/Dialog/utils'
|
||||
|
||||
export const Input = createInput(TextInput)
|
||||
|
||||
export function Outer({
|
||||
export function Outer(props: React.PropsWithChildren<DialogOuterProps>) {
|
||||
if (props.type === 'alert') {
|
||||
return <OuterAlert {...props} />
|
||||
}
|
||||
return <OuterSheet {...props} />
|
||||
}
|
||||
|
||||
function OuterAlert({
|
||||
children,
|
||||
control,
|
||||
onClose,
|
||||
testID,
|
||||
}: React.PropsWithChildren<DialogOuterProps>) {
|
||||
const t = useTheme()
|
||||
const [isOpen, setIsOpen] = React.useState(false)
|
||||
const closeCallbacks = React.useRef<(() => void)[]>([])
|
||||
const {setDialogIsOpen} = useDialogStateControlContext()
|
||||
|
||||
const callQueuedCallbacks = React.useCallback(() => {
|
||||
for (const cb of closeCallbacks.current) {
|
||||
try {
|
||||
cb()
|
||||
} catch (e: any) {
|
||||
logger.error(e || 'Error running close callback')
|
||||
}
|
||||
}
|
||||
closeCallbacks.current = []
|
||||
}, [])
|
||||
|
||||
const open = React.useCallback<DialogControlProps['open']>(() => {
|
||||
callQueuedCallbacks()
|
||||
setDialogIsOpen(control.id, true)
|
||||
setIsOpen(true)
|
||||
}, [setDialogIsOpen, control.id, callQueuedCallbacks])
|
||||
|
||||
const close = React.useCallback<DialogControlProps['close']>(cb => {
|
||||
if (typeof cb === 'function') {
|
||||
closeCallbacks.current.push(cb)
|
||||
}
|
||||
setIsOpen(false)
|
||||
}, [])
|
||||
|
||||
const onDismiss = React.useCallback(() => {
|
||||
setDialogIsOpen(control.id, false)
|
||||
callQueuedCallbacks()
|
||||
onClose?.()
|
||||
}, [callQueuedCallbacks, control.id, onClose, setDialogIsOpen])
|
||||
|
||||
useImperativeHandle(
|
||||
control.ref,
|
||||
() => ({
|
||||
open,
|
||||
close,
|
||||
}),
|
||||
[open, close],
|
||||
)
|
||||
|
||||
const context = React.useMemo(
|
||||
() => ({
|
||||
close,
|
||||
isNativeDialog: true,
|
||||
nativeSnapPoint: BottomSheetSnapPoint.Hidden,
|
||||
disableDrag: false,
|
||||
setDisableDrag: () => {},
|
||||
isWithinDialog: true,
|
||||
type: 'alert' as const,
|
||||
}),
|
||||
[close],
|
||||
)
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={isOpen}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={() => close()}
|
||||
onDismiss={onDismiss}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
onPress={() => close()}
|
||||
style={[
|
||||
a.flex_1,
|
||||
a.justify_center,
|
||||
a.align_center,
|
||||
a.px_xl,
|
||||
{backgroundColor: 'rgba(0,0,0,0.5)'},
|
||||
]}>
|
||||
<Pressable
|
||||
accessibilityRole="button"
|
||||
onPress={e => e.stopPropagation()}
|
||||
style={[
|
||||
t.atoms.bg,
|
||||
a.w_full,
|
||||
{borderRadius: 48, maxWidth: 320},
|
||||
a.overflow_hidden,
|
||||
]}>
|
||||
<Context.Provider value={context}>
|
||||
<View testID={testID}>{children}</View>
|
||||
</Context.Provider>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
function OuterSheet({
|
||||
children,
|
||||
control,
|
||||
onClose,
|
||||
@@ -155,6 +261,7 @@ export function Outer({
|
||||
disableDrag,
|
||||
setDisableDrag,
|
||||
isWithinDialog: true,
|
||||
type: 'sheet' as const,
|
||||
}),
|
||||
[close, snapPoint, disableDrag, setDisableDrag],
|
||||
)
|
||||
@@ -206,8 +313,18 @@ export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
|
||||
{children, contentContainerStyle, header, ...props},
|
||||
ref,
|
||||
) {
|
||||
const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext()
|
||||
const {nativeSnapPoint, disableDrag, setDisableDrag, type} =
|
||||
useDialogContext()
|
||||
const insets = useSafeAreaInsets()
|
||||
|
||||
if (type === 'alert') {
|
||||
return (
|
||||
<View style={[a.p_2xl, contentContainerStyle]} {...props}>
|
||||
{header}
|
||||
{children}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
const isAtMaxSnapPoint = nativeSnapPoint === BottomSheetSnapPoint.Full
|
||||
|
||||
let paddingBottom = 0
|
||||
@@ -375,7 +492,11 @@ export function Handle({
|
||||
const t = useTheme()
|
||||
const {_} = useLingui()
|
||||
const {screenReaderEnabled} = useA11y()
|
||||
const {close} = useDialogContext()
|
||||
const {close, type} = useDialogContext()
|
||||
|
||||
if (type === 'alert') {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<View style={[a.absolute, a.w_full, a.align_center, a.z_10, {height: 20}]}>
|
||||
|
||||
@@ -45,6 +45,7 @@ export function Outer({
|
||||
control,
|
||||
onClose,
|
||||
webOptions,
|
||||
type = 'sheet',
|
||||
}: React.PropsWithChildren<DialogOuterProps>) {
|
||||
const {_} = useLingui()
|
||||
const {gtMobile} = useBreakpoints()
|
||||
@@ -96,6 +97,8 @@ export function Outer({
|
||||
[close, open],
|
||||
)
|
||||
|
||||
const isAlert = type === 'alert'
|
||||
|
||||
const context = React.useMemo(
|
||||
() => ({
|
||||
close,
|
||||
@@ -104,8 +107,9 @@ export function Outer({
|
||||
disableDrag: false,
|
||||
setDisableDrag: () => {},
|
||||
isWithinDialog: true,
|
||||
type,
|
||||
}),
|
||||
[close],
|
||||
[close, type],
|
||||
)
|
||||
|
||||
return (
|
||||
@@ -124,7 +128,9 @@ export function Outer({
|
||||
a.inset_0,
|
||||
a.z_10,
|
||||
a.px_xl,
|
||||
webOptions?.alignCenter ? a.justify_center : undefined,
|
||||
webOptions?.alignCenter || isAlert
|
||||
? a.justify_center
|
||||
: undefined,
|
||||
a.align_center,
|
||||
{
|
||||
overflowY: 'auto',
|
||||
@@ -165,9 +171,10 @@ export function Inner({
|
||||
contentContainerStyle,
|
||||
}: DialogInnerProps) {
|
||||
const t = useTheme()
|
||||
const {close} = React.useContext(Context)
|
||||
const {close, type} = React.useContext(Context)
|
||||
const {gtMobile} = useBreakpoints()
|
||||
const {reduceMotionEnabled} = useA11y()
|
||||
const isAlert = type === 'alert'
|
||||
FocusGuards.useFocusGuards()
|
||||
return (
|
||||
<FocusScope.FocusScope loop asChild trapped>
|
||||
@@ -189,12 +196,13 @@ export function Inner({
|
||||
a.border,
|
||||
t.atoms.bg,
|
||||
{
|
||||
maxWidth: 600,
|
||||
maxWidth: isAlert ? 320 : 600,
|
||||
borderColor: t.palette.contrast_200,
|
||||
shadowColor: t.palette.black,
|
||||
shadowOpacity: t.name === 'light' ? 0.1 : 0.4,
|
||||
shadowRadius: 30,
|
||||
},
|
||||
isAlert && {borderRadius: 36},
|
||||
!reduceMotionEnabled && a.zoom_fade_in,
|
||||
style,
|
||||
])}>
|
||||
|
||||
@@ -45,6 +45,7 @@ export type DialogContextProps = {
|
||||
setDisableDrag: React.Dispatch<React.SetStateAction<boolean>>
|
||||
// in the event that the hook is used outside of a dialog
|
||||
isWithinDialog: boolean
|
||||
type: 'sheet' | 'alert'
|
||||
}
|
||||
|
||||
export type DialogControlOpenOptions = {
|
||||
@@ -65,6 +66,12 @@ export type DialogOuterProps = {
|
||||
alignCenter?: boolean
|
||||
onBackgroundPress?: (e: GestureResponderEvent) => void
|
||||
}
|
||||
/**
|
||||
* The presentation style of the dialog.
|
||||
* - `sheet` (default): Bottom sheet on native, standard modal on web.
|
||||
* - `alert`: Centered alert modal on all platforms.
|
||||
*/
|
||||
type?: 'sheet' | 'alert'
|
||||
testID?: string
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user