add detached variant to modal
This commit is contained in:
@@ -23,6 +23,7 @@ export const Context = createContext<DialogContextProps>({
|
|||||||
disableDrag: false,
|
disableDrag: false,
|
||||||
setDisableDrag: () => {},
|
setDisableDrag: () => {},
|
||||||
isWithinDialog: false,
|
isWithinDialog: false,
|
||||||
|
type: 'sheet',
|
||||||
})
|
})
|
||||||
Context.displayName = 'DialogContext'
|
Context.displayName = 'DialogContext'
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, {useImperativeHandle} from 'react'
|
import React, {useImperativeHandle} from 'react'
|
||||||
import {
|
import {
|
||||||
type LayoutChangeEvent,
|
type LayoutChangeEvent,
|
||||||
|
Modal,
|
||||||
type NativeScrollEvent,
|
type NativeScrollEvent,
|
||||||
type NativeSyntheticEvent,
|
type NativeSyntheticEvent,
|
||||||
Pressable,
|
Pressable,
|
||||||
@@ -49,7 +50,112 @@ export * from '#/components/Dialog/utils'
|
|||||||
|
|
||||||
export const Input = createInput(TextInput)
|
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,
|
children,
|
||||||
control,
|
control,
|
||||||
onClose,
|
onClose,
|
||||||
@@ -155,6 +261,7 @@ export function Outer({
|
|||||||
disableDrag,
|
disableDrag,
|
||||||
setDisableDrag,
|
setDisableDrag,
|
||||||
isWithinDialog: true,
|
isWithinDialog: true,
|
||||||
|
type: 'sheet' as const,
|
||||||
}),
|
}),
|
||||||
[close, snapPoint, disableDrag, setDisableDrag],
|
[close, snapPoint, disableDrag, setDisableDrag],
|
||||||
)
|
)
|
||||||
@@ -206,8 +313,18 @@ export const ScrollableInner = React.forwardRef<ScrollView, DialogInnerProps>(
|
|||||||
{children, contentContainerStyle, header, ...props},
|
{children, contentContainerStyle, header, ...props},
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const {nativeSnapPoint, disableDrag, setDisableDrag} = useDialogContext()
|
const {nativeSnapPoint, disableDrag, setDisableDrag, type} =
|
||||||
|
useDialogContext()
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
|
|
||||||
|
if (type === 'alert') {
|
||||||
|
return (
|
||||||
|
<View style={[a.p_2xl, contentContainerStyle]} {...props}>
|
||||||
|
{header}
|
||||||
|
{children}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
const isAtMaxSnapPoint = nativeSnapPoint === BottomSheetSnapPoint.Full
|
const isAtMaxSnapPoint = nativeSnapPoint === BottomSheetSnapPoint.Full
|
||||||
|
|
||||||
let paddingBottom = 0
|
let paddingBottom = 0
|
||||||
@@ -375,7 +492,11 @@ export function Handle({
|
|||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {screenReaderEnabled} = useA11y()
|
const {screenReaderEnabled} = useA11y()
|
||||||
const {close} = useDialogContext()
|
const {close, type} = useDialogContext()
|
||||||
|
|
||||||
|
if (type === 'alert') {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[a.absolute, a.w_full, a.align_center, a.z_10, {height: 20}]}>
|
<View style={[a.absolute, a.w_full, a.align_center, a.z_10, {height: 20}]}>
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export function Outer({
|
|||||||
control,
|
control,
|
||||||
onClose,
|
onClose,
|
||||||
webOptions,
|
webOptions,
|
||||||
|
type = 'sheet',
|
||||||
}: React.PropsWithChildren<DialogOuterProps>) {
|
}: React.PropsWithChildren<DialogOuterProps>) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
@@ -96,6 +97,8 @@ export function Outer({
|
|||||||
[close, open],
|
[close, open],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const isAlert = type === 'alert'
|
||||||
|
|
||||||
const context = React.useMemo(
|
const context = React.useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
close,
|
close,
|
||||||
@@ -104,8 +107,9 @@ export function Outer({
|
|||||||
disableDrag: false,
|
disableDrag: false,
|
||||||
setDisableDrag: () => {},
|
setDisableDrag: () => {},
|
||||||
isWithinDialog: true,
|
isWithinDialog: true,
|
||||||
|
type,
|
||||||
}),
|
}),
|
||||||
[close],
|
[close, type],
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -124,7 +128,9 @@ export function Outer({
|
|||||||
a.inset_0,
|
a.inset_0,
|
||||||
a.z_10,
|
a.z_10,
|
||||||
a.px_xl,
|
a.px_xl,
|
||||||
webOptions?.alignCenter ? a.justify_center : undefined,
|
webOptions?.alignCenter || isAlert
|
||||||
|
? a.justify_center
|
||||||
|
: undefined,
|
||||||
a.align_center,
|
a.align_center,
|
||||||
{
|
{
|
||||||
overflowY: 'auto',
|
overflowY: 'auto',
|
||||||
@@ -165,9 +171,10 @@ export function Inner({
|
|||||||
contentContainerStyle,
|
contentContainerStyle,
|
||||||
}: DialogInnerProps) {
|
}: DialogInnerProps) {
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
const {close} = React.useContext(Context)
|
const {close, type} = React.useContext(Context)
|
||||||
const {gtMobile} = useBreakpoints()
|
const {gtMobile} = useBreakpoints()
|
||||||
const {reduceMotionEnabled} = useA11y()
|
const {reduceMotionEnabled} = useA11y()
|
||||||
|
const isAlert = type === 'alert'
|
||||||
FocusGuards.useFocusGuards()
|
FocusGuards.useFocusGuards()
|
||||||
return (
|
return (
|
||||||
<FocusScope.FocusScope loop asChild trapped>
|
<FocusScope.FocusScope loop asChild trapped>
|
||||||
@@ -189,12 +196,13 @@ export function Inner({
|
|||||||
a.border,
|
a.border,
|
||||||
t.atoms.bg,
|
t.atoms.bg,
|
||||||
{
|
{
|
||||||
maxWidth: 600,
|
maxWidth: isAlert ? 320 : 600,
|
||||||
borderColor: t.palette.contrast_200,
|
borderColor: t.palette.contrast_200,
|
||||||
shadowColor: t.palette.black,
|
shadowColor: t.palette.black,
|
||||||
shadowOpacity: t.name === 'light' ? 0.1 : 0.4,
|
shadowOpacity: t.name === 'light' ? 0.1 : 0.4,
|
||||||
shadowRadius: 30,
|
shadowRadius: 30,
|
||||||
},
|
},
|
||||||
|
isAlert && {borderRadius: 36},
|
||||||
!reduceMotionEnabled && a.zoom_fade_in,
|
!reduceMotionEnabled && a.zoom_fade_in,
|
||||||
style,
|
style,
|
||||||
])}>
|
])}>
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export type DialogContextProps = {
|
|||||||
setDisableDrag: React.Dispatch<React.SetStateAction<boolean>>
|
setDisableDrag: React.Dispatch<React.SetStateAction<boolean>>
|
||||||
// in the event that the hook is used outside of a dialog
|
// in the event that the hook is used outside of a dialog
|
||||||
isWithinDialog: boolean
|
isWithinDialog: boolean
|
||||||
|
type: 'sheet' | 'alert'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type DialogControlOpenOptions = {
|
export type DialogControlOpenOptions = {
|
||||||
@@ -65,6 +66,12 @@ export type DialogOuterProps = {
|
|||||||
alignCenter?: boolean
|
alignCenter?: boolean
|
||||||
onBackgroundPress?: (e: GestureResponderEvent) => void
|
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
|
testID?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user