about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/components/Dialog/index.tsx121
-rw-r--r--src/components/Dialog/index.web.tsx57
-rw-r--r--src/components/Dialog/types.ts21
-rw-r--r--src/components/Prompt.tsx2
-rw-r--r--src/components/icons/Times.tsx5
-rw-r--r--src/view/screens/Storybook/Dialogs.tsx2
6 files changed, 123 insertions, 85 deletions
diff --git a/src/components/Dialog/index.tsx b/src/components/Dialog/index.tsx
index 9132e68de..f30d24e6a 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<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,77 +71,80 @@ export function Outer({
     (index: number) => {
       if (index === -1) {
         onClose?.()
+        setOpenIndex(-1)
       }
     },
-    [onClose],
+    [onClose, setOpenIndex],
   )
 
   const context = React.useMemo(() => ({close}), [close])
 
   return (
-    <Portal>
-      <BottomSheet
-        enableDynamicSizing={!hasSnapPoints}
-        enablePanDownToClose
-        keyboardBehavior="interactive"
-        android_keyboardInputMode="adjustResize"
-        keyboardBlurBehavior="restore"
-        topInset={insets.top}
-        {...sheetOptions}
-        ref={sheet}
-        index={-1}
-        backgroundStyle={{backgroundColor: 'transparent'}}
-        backdropComponent={props => (
-          <BottomSheetBackdrop
-            opacity={0.4}
-            appearsOnIndex={0}
-            disappearsOnIndex={-1}
-            {...props}
-          />
-        )}
-        handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
-        handleStyle={{display: 'none'}}
-        onChange={onChange}>
-        <Context.Provider value={context}>
-          <View
-            style={[
-              a.absolute,
-              a.inset_0,
-              t.atoms.bg,
-              {
-                borderTopLeftRadius: 40,
-                borderTopRightRadius: 40,
-                height: Dimensions.get('window').height * 2,
-              },
-            ]}
-          />
-          {children}
-        </Context.Provider>
-      </BottomSheet>
-    </Portal>
+    isOpen && (
+      <Portal>
+        <BottomSheet
+          enableDynamicSizing={!hasSnapPoints}
+          enablePanDownToClose
+          keyboardBehavior="interactive"
+          android_keyboardInputMode="adjustResize"
+          keyboardBlurBehavior="restore"
+          topInset={insets.top}
+          {...sheetOptions}
+          ref={sheet}
+          index={openIndex}
+          backgroundStyle={{backgroundColor: 'transparent'}}
+          backdropComponent={props => (
+            <BottomSheetBackdrop
+              opacity={0.4}
+              appearsOnIndex={0}
+              disappearsOnIndex={-1}
+              {...props}
+            />
+          )}
+          handleIndicatorStyle={{backgroundColor: t.palette.primary_500}}
+          handleStyle={{display: 'none'}}
+          onChange={onChange}>
+          <Context.Provider value={context}>
+            <View
+              style={[
+                a.absolute,
+                a.inset_0,
+                t.atoms.bg,
+                {
+                  borderTopLeftRadius: 40,
+                  borderTopRightRadius: 40,
+                  height: Dimensions.get('window').height * 2,
+                },
+              ]}
+            />
+            {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>
   )
diff --git a/src/components/Dialog/index.web.tsx b/src/components/Dialog/index.web.tsx
index 305c00e97..79441fb5e 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<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>
+  )
+}
diff --git a/src/components/Dialog/types.ts b/src/components/Dialog/types.ts
index d36784183..00178926a 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<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
diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx
index 2c79d27cf..411679102 100644
--- a/src/components/Prompt.tsx
+++ b/src/components/Prompt.tsx
@@ -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>
diff --git a/src/components/icons/Times.tsx b/src/components/icons/Times.tsx
new file mode 100644
index 000000000..678ac3fcb
--- /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 db568c6bd..a18b3c62f 100644
--- a/src/view/screens/Storybook/Dialogs.tsx
+++ b/src/view/screens/Storybook/Dialogs.tsx
@@ -50,7 +50,7 @@ export function Dialogs() {
 
       <Dialog.Outer
         control={control}
-        nativeOptions={{sheet: {snapPoints: ['90%']}}}>
+        nativeOptions={{sheet: {snapPoints: ['100%']}}}>
         <Dialog.Handle />
 
         <Dialog.ScrollableInner