diff --git a/src/alf/index.tsx b/src/alf/index.tsx
index 1daa0bfedc..69a8798539 100644
--- a/src/alf/index.tsx
+++ b/src/alf/index.tsx
@@ -5,6 +5,7 @@ import * as themes from '#/alf/themes'
export * as tokens from '#/alf/tokens'
export {atoms} from '#/alf/atoms'
export * from '#/alf/util/platform'
+export * from '#/alf/util/flatten'
type BreakpointName = keyof typeof breakpoints
diff --git a/src/alf/themes.ts b/src/alf/themes.ts
index 14e1de7ff6..f26f10a6f2 100644
--- a/src/alf/themes.ts
+++ b/src/alf/themes.ts
@@ -11,12 +11,36 @@ export const lightPalette = {
primary: tokens.color.blue_500,
positive: tokens.color.green_500,
negative: tokens.color.red_500,
+
+ contrast_0: tokens.color.gray_0,
+ contrast_100: tokens.color.gray_100,
+ contrast_200: tokens.color.gray_200,
+ contrast_300: tokens.color.gray_300,
+ contrast_400: tokens.color.gray_400,
+ contrast_500: tokens.color.gray_500,
+ contrast_600: tokens.color.gray_600,
+ contrast_700: tokens.color.gray_700,
+ contrast_800: tokens.color.gray_800,
+ contrast_900: tokens.color.gray_900,
+ contrast_1000: tokens.color.gray_1000,
} as const
export const darkPalette: Palette = {
primary: tokens.color.blue_500,
positive: tokens.color.green_400,
negative: tokens.color.red_400,
+
+ contrast_0: tokens.color.gray_1000,
+ contrast_100: tokens.color.gray_900,
+ contrast_200: tokens.color.gray_800,
+ contrast_300: tokens.color.gray_700,
+ contrast_400: tokens.color.gray_600,
+ contrast_500: tokens.color.gray_500,
+ contrast_600: tokens.color.gray_400,
+ contrast_700: tokens.color.gray_300,
+ contrast_800: tokens.color.gray_200,
+ contrast_900: tokens.color.gray_100,
+ contrast_1000: tokens.color.gray_0,
} as const
export const light = {
diff --git a/src/alf/util/flatten.ts b/src/alf/util/flatten.ts
new file mode 100644
index 0000000000..448716a082
--- /dev/null
+++ b/src/alf/util/flatten.ts
@@ -0,0 +1,3 @@
+import {StyleSheet} from 'react-native'
+
+export const flatten = StyleSheet.flatten
diff --git a/src/view/com/Button.tsx b/src/view/com/Button.tsx
index 7b3beb7dca..51fe1e3541 100644
--- a/src/view/com/Button.tsx
+++ b/src/view/com/Button.tsx
@@ -284,13 +284,7 @@ export function ButtonText({
return (
+ style={[atoms.font_semibold, atoms.text_center, ...textStyles, style]}>
{children}
)
diff --git a/src/view/com/Dialog/context.ts b/src/view/com/Dialog/context.ts
index bd150f8cbc..2330969933 100644
--- a/src/view/com/Dialog/context.ts
+++ b/src/view/com/Dialog/context.ts
@@ -15,5 +15,9 @@ export function useDialogControl() {
close: () => {},
})
- return control
+ return {
+ ref: control,
+ open: () => control.current.open(),
+ close: () => control.current.close(),
+ }
}
diff --git a/src/view/com/Dialog/index.tsx b/src/view/com/Dialog/index.tsx
index 6d6946ec44..7f7a630a14 100644
--- a/src/view/com/Dialog/index.tsx
+++ b/src/view/com/Dialog/index.tsx
@@ -12,12 +12,14 @@ import {
} from '#/view/com/Dialog/types'
import {Context} from '#/view/com/Dialog/context'
-export {useDialogControl} from '#/view/com/Dialog/context'
+export {useDialogControl, useDialogContext} from '#/view/com/Dialog/context'
+export * from '#/view/com/Dialog/types'
export function Outer({
+ children,
control,
onClose,
- children,
+ nativeOptions,
}: React.PropsWithChildren) {
const t = useTheme()
const sheet = React.useRef(null)
@@ -32,7 +34,7 @@ export function Outer({
}, [onClose])
useImperativeHandle(
- control,
+ control.ref,
() => ({
open,
close,
@@ -45,13 +47,14 @@ export function Outer({
return (
(
{}}
onClose={onClose}>
- {children}
+
+
+ {children}
+
)
}
+// TODO a11y props here, or is that handled by the sheet?
export function Inner(props: DialogInnerProps) {
- const t = useTheme()
return (
{props.children}
@@ -101,7 +113,7 @@ export function Handle() {
t.atoms.bg_contrast_200,
{
top: 12,
- width: 80,
+ width: 50,
height: 6,
alignSelf: 'center',
},
diff --git a/src/view/com/Dialog/index.web.tsx b/src/view/com/Dialog/index.web.tsx
index ee390daba1..37d4cebc85 100644
--- a/src/view/com/Dialog/index.web.tsx
+++ b/src/view/com/Dialog/index.web.tsx
@@ -11,7 +11,8 @@ import {Button} from '#/view/com/Button'
import {DialogOuterProps, DialogInnerProps} from '#/view/com/Dialog/types'
import {Context, useDialogContext} from '#/view/com/Dialog/context'
-export {useDialogControl} from '#/view/com/Dialog/context'
+export {useDialogControl, useDialogContext} from '#/view/com/Dialog/context'
+export * from '#/view/com/Dialog/types'
const stopPropagation = (e: any) => e.stopPropagation()
@@ -38,7 +39,7 @@ export function Outer({
}, [onClose, setIsOpen])
useImperativeHandle(
- control,
+ control.ref,
() => ({
open,
close,
@@ -58,7 +59,12 @@ export function Outer({
return () => document.removeEventListener('keydown', handler)
}, [isOpen, close])
- const context = React.useMemo(() => ({close}), [close])
+ const context = React.useMemo(
+ () => ({
+ close,
+ }),
+ [close],
+ )
return (
<>
@@ -111,12 +117,19 @@ export function Outer({
)
}
-export function Inner({children, style}: DialogInnerProps) {
+export function Inner({
+ children,
+ style,
+ accessibilityLabelledBy,
+ accessibilityDescribedBy,
+}: DialogInnerProps) {
const t = useTheme()
const {gtMobile} = useBreakpoints()
return (
{children}
diff --git a/src/view/com/Dialog/types.ts b/src/view/com/Dialog/types.ts
index 4c41849aa3..cdcad90da6 100644
--- a/src/view/com/Dialog/types.ts
+++ b/src/view/com/Dialog/types.ts
@@ -1,5 +1,8 @@
import React from 'react'
-import type {ViewStyle} from 'react-native'
+import type {ViewStyle, AccessibilityProps} from 'react-native'
+import {BottomSheetProps} from '@gorhom/bottom-sheet'
+
+type A11yProps = Required
export type DialogContextProps = {
close: () => void
@@ -11,8 +14,20 @@ export type DialogControlProps = {
}
export type DialogOuterProps = {
- control: React.RefObject
+ control: {
+ ref: React.RefObject
+ open: (index?: number) => void
+ close: () => void
+ }
onClose?: () => void
+ nativeOptions?: {
+ sheet?: Omit
+ }
+ webOptions?: {}
}
-export type DialogInnerProps = React.PropsWithChildren<{style?: ViewStyle}>
+export type DialogInnerProps = React.PropsWithChildren<{
+ style?: ViewStyle
+ accessibilityLabelledBy: A11yProps['aria-labelledby']
+ accessibilityDescribedBy: string
+}>
diff --git a/src/view/com/Prompt.tsx b/src/view/com/Prompt.tsx
new file mode 100644
index 0000000000..fc55bc4a51
--- /dev/null
+++ b/src/view/com/Prompt.tsx
@@ -0,0 +1,134 @@
+import React from 'react'
+import {View, PressableProps, LayoutChangeEvent} from 'react-native'
+import {useSafeAreaInsets} from 'react-native-safe-area-context'
+
+import {useTheme, atoms as a} from '#/alf'
+import {H2, P} from '#/view/com/Typography'
+import {Button} from '#/view/com/Button'
+
+import * as Dialog from '#/view/com/Dialog'
+
+export {useDialogControl as usePromptControl} from '#/view/com/Dialog'
+
+const Context = React.createContext<{
+ titleId: string
+ descriptionId: string
+}>({
+ titleId: '',
+ descriptionId: '',
+})
+
+export function Outer({
+ children,
+ control,
+}: React.PropsWithChildren<{
+ control: Dialog.DialogOuterProps['control']
+}>) {
+ const insets = useSafeAreaInsets()
+ const titleId = React.useId()
+ const descriptionId = React.useId()
+ const [defaultSnapPoints, setDefaultSnapPoints] = React.useState<
+ string | number
+ >('25%')
+
+ const context = React.useMemo(
+ () => ({titleId, descriptionId}),
+ [titleId, descriptionId],
+ )
+
+ const measureDefaultSnapPoint = React.useCallback(
+ (e: LayoutChangeEvent) => {
+ setDefaultSnapPoints(e.nativeEvent.layout.height + insets.bottom + 50)
+ },
+ [insets, setDefaultSnapPoints],
+ )
+
+ return (
+
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+export function Title({children}: React.PropsWithChildren<{}>) {
+ const t = useTheme()
+ const {titleId} = React.useContext(Context)
+ return (
+
+ {children}
+
+ )
+}
+
+export function Description({children}: React.PropsWithChildren<{}>) {
+ const t = useTheme()
+ const {descriptionId} = React.useContext(Context)
+ return (
+
+ {children}
+
+ )
+}
+
+export function Actions({children}: React.PropsWithChildren<{}>) {
+ return (
+
+ {children}
+
+ )
+}
+
+export function Cancel({
+ children,
+}: React.PropsWithChildren<{onPress?: PressableProps['onPress']}>) {
+ const {close} = Dialog.useDialogContext()
+ return (
+
+ )
+}
+
+export function Action({
+ children,
+ onPress,
+}: React.PropsWithChildren<{onPress?: () => void}>) {
+ const {close} = Dialog.useDialogContext()
+ const handleOnPress = React.useCallback(() => {
+ close()
+ onPress?.()
+ }, [close, onPress])
+ return (
+
+ )
+}
diff --git a/src/view/com/Typography.tsx b/src/view/com/Typography.tsx
index 6579c2e511..029fc2e24f 100644
--- a/src/view/com/Typography.tsx
+++ b/src/view/com/Typography.tsx
@@ -1,6 +1,7 @@
import React from 'react'
import {Text as RNText, TextProps} from 'react-native'
-import {useTheme, atoms, web} from '#/alf'
+
+import {useTheme, atoms, web, flatten} from '#/alf'
export function Text({style, ...rest}: TextProps) {
const t = useTheme()
@@ -18,7 +19,7 @@ export function H1({style, ...rest}: TextProps) {
)
}
@@ -34,7 +35,7 @@ export function H2({style, ...rest}: TextProps) {
)
}
@@ -50,7 +51,7 @@ export function H3({style, ...rest}: TextProps) {
)
}
@@ -66,7 +67,7 @@ export function H4({style, ...rest}: TextProps) {
)
}
@@ -82,7 +83,7 @@ export function H5({style, ...rest}: TextProps) {
)
}
@@ -98,7 +99,26 @@ export function H6({style, ...rest}: TextProps) {
+ )
+}
+
+export function P({style, ...rest}: TextProps) {
+ const t = useTheme()
+ const attr =
+ web({
+ role: 'paragraph',
+ }) || {}
+ const _style = flatten(style)
+ const lineHeight =
+ (_style?.lineHeight || atoms.text_md.lineHeight) *
+ atoms.leading_normal.lineHeight
+ return (
+
)
}
diff --git a/src/view/screens/DebugNew.tsx b/src/view/screens/DebugNew.tsx
index 6abcc438b7..27b993c95e 100644
--- a/src/view/screens/DebugNew.tsx
+++ b/src/view/screens/DebugNew.tsx
@@ -8,32 +8,39 @@ import * as tokens from '#/alf/tokens'
import {atoms as a, useTheme, useBreakpoints, ThemeProvider as Alf} from '#/alf'
import {Button, ButtonText} from '#/view/com/Button'
import {Link} from '#/view/com/Link'
-import {Text, H1, H2, H3, H4, H5, H6} from '#/view/com/Typography'
+import {Text, H1, H2, H3, H4, H5, H6, P} from '#/view/com/Typography'
import {InputText} from '#/view/com/forms/InputText'
import {InputDate, utils} from '#/view/com/forms/InputDate'
import {Logo} from '#/view/icons/Logo'
import * as Dialog from '#/view/com/Dialog'
+import * as Prompt from '#/view/com/Prompt'
function ThemeSelector() {
const setColorMode = useSetColorMode()
return (
-
+
@@ -319,39 +326,58 @@ function Forms() {
function Dialogs() {
const control = Dialog.useDialogControl()
+ const prompt = Prompt.usePromptControl()
return (
<>
+
+
+
+ Are you sure?
+
+ This action cannot be undone. This action cannot be undone. This
+ action cannot be undone.
+
+
+ Cancel
+ Confirm
+
+
+
-
+
-
-
-
+ Dialog
+ Description
+
+
+