From b52a742925cff4429885e94815d61f3f7cfb5a66 Mon Sep 17 00:00:00 2001 From: Eric Bailey Date: Mon, 19 Feb 2024 18:18:13 -0600 Subject: [PATCH] Improve dialogs (#2933) * Improve dialogs * Remove comment, revert storybook * Hacky fix * Comments --- src/components/Dialog/index.tsx | 121 ++++++++++++++----------- src/components/Dialog/index.web.tsx | 57 ++++++------ src/components/Dialog/types.ts | 21 +++-- src/components/Prompt.tsx | 2 +- src/components/icons/Times.tsx | 5 + src/view/screens/Storybook/Dialogs.tsx | 2 +- 6 files changed, 123 insertions(+), 85 deletions(-) create mode 100644 src/components/icons/Times.tsx diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx index 9132e68d..f30d24e6 100644 --- a/src/components/Dialog/index.tsx +++ b/src/components/Dialog/index.tsx @@ -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((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( + ({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,77 +71,80 @@ export function Outer({ (index: number) => { if (index === -1) { onClose?.() + setOpenIndex(-1) } }, - [onClose], + [onClose, setOpenIndex], ) const context = React.useMemo(() => ({close}), [close]) return ( - - ( - - )} - handleIndicatorStyle={{backgroundColor: t.palette.primary_500}} - handleStyle={{display: 'none'}} - onChange={onChange}> - - - {children} - - - + isOpen && ( + + ( + + )} + handleIndicatorStyle={{backgroundColor: t.palette.primary_500}} + handleStyle={{display: 'none'}} + onChange={onChange}> + + + {hasSnapPoints ? children : {children}} + + + + ) ) } -// 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 ( - {props.children} + {children} ) } -export function ScrollableInner(props: DialogInnerProps) { +export function ScrollableInner({children, style}: DialogInnerProps) { const insets = useSafeAreaInsets() return ( - {props.children} + {children} ) diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx index 305c00e9..79441fb5 100644 --- a/src/components/Dialog/index.web.tsx +++ b/src/components/Dialog/index.web.tsx @@ -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) { 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} @@ -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 ( -// -// -// -// ) -// } +export function Close() { + const {_} = useLingui() + const {close} = React.useContext(Context) + return ( + + + + ) +} diff --git a/src/components/Dialog/types.ts b/src/components/Dialog/types.ts index d3678418..00178926 100644 --- a/src/components/Dialog/types.ts +++ b/src/components/Dialog/types.ts @@ -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 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 = React.PropsWithChildren<{ - style?: ViewStyle -}> & - T +type DialogInnerPropsBase = React.PropsWithChildren & T export type DialogInnerProps = | DialogInnerPropsBase<{ label?: undefined diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx index 2c79d27c..41167910 100644 --- a/src/components/Prompt.tsx +++ b/src/components/Prompt.tsx @@ -41,7 +41,7 @@ export function Outer({ + style={[{width: 'auto', maxWidth: 400}]}> {children} diff --git a/src/components/icons/Times.tsx b/src/components/icons/Times.tsx new file mode 100644 index 00000000..678ac3fc --- /dev/null +++ b/src/components/icons/Times.tsx @@ -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', +}) diff --git a/src/view/screens/Storybook/Dialogs.tsx b/src/view/screens/Storybook/Dialogs.tsx index db568c6b..a18b3c62 100644 --- a/src/view/screens/Storybook/Dialogs.tsx +++ b/src/view/screens/Storybook/Dialogs.tsx @@ -50,7 +50,7 @@ export function Dialogs() { + nativeOptions={{sheet: {snapPoints: ['100%']}}}>