template_0205
This commit is contained in:
276
RN_TEMPLATE/app/components/Dialog.tsx
Normal file
276
RN_TEMPLATE/app/components/Dialog.tsx
Normal file
@@ -0,0 +1,276 @@
|
||||
import { Modal as RNModal, Pressable, StyleProp, TextStyle, View, ViewStyle } from "react-native"
|
||||
|
||||
import { useAppTheme } from "@/theme/context"
|
||||
import type { ThemedStyle } from "@/theme/types"
|
||||
import { s } from "@/utils/responsive"
|
||||
|
||||
import { Button } from "./Button"
|
||||
import { Text, TextProps } from "./Text"
|
||||
|
||||
type Presets = "default" | "destructive"
|
||||
|
||||
export interface DialogProps {
|
||||
/**
|
||||
* Whether the dialog is visible.
|
||||
*/
|
||||
visible: boolean
|
||||
/**
|
||||
* Callback when the dialog is closed.
|
||||
*/
|
||||
onClose: () => void
|
||||
/**
|
||||
* Dialog preset style.
|
||||
* - `default`: Normal dialog with filled confirm button
|
||||
* - `destructive`: Dangerous action with red confirm button
|
||||
* @default "default"
|
||||
*/
|
||||
preset?: Presets
|
||||
/**
|
||||
* Title text which is looked up via i18n.
|
||||
*/
|
||||
titleTx?: TextProps["tx"]
|
||||
/**
|
||||
* Title text to display if not using `titleTx`.
|
||||
*/
|
||||
title?: string
|
||||
/**
|
||||
* Optional title options to pass to i18n.
|
||||
*/
|
||||
titleTxOptions?: TextProps["txOptions"]
|
||||
/**
|
||||
* Message text which is looked up via i18n.
|
||||
*/
|
||||
messageTx?: TextProps["tx"]
|
||||
/**
|
||||
* Message text to display if not using `messageTx`.
|
||||
*/
|
||||
message?: string
|
||||
/**
|
||||
* Optional message options to pass to i18n.
|
||||
*/
|
||||
messageTxOptions?: TextProps["txOptions"]
|
||||
/**
|
||||
* Confirm button text which is looked up via i18n.
|
||||
* @default "common:ok"
|
||||
*/
|
||||
confirmTx?: TextProps["tx"]
|
||||
/**
|
||||
* Confirm button text to display if not using `confirmTx`.
|
||||
*/
|
||||
confirmText?: string
|
||||
/**
|
||||
* Callback when confirm button is pressed.
|
||||
*/
|
||||
onConfirm?: () => void
|
||||
/**
|
||||
* Cancel button text which is looked up via i18n.
|
||||
* @default "common:cancel"
|
||||
*/
|
||||
cancelTx?: TextProps["tx"]
|
||||
/**
|
||||
* Cancel button text to display if not using `cancelTx`.
|
||||
*/
|
||||
cancelText?: string
|
||||
/**
|
||||
* Callback when cancel button is pressed. If not provided, cancel button will not be shown.
|
||||
*/
|
||||
onCancel?: () => void
|
||||
/**
|
||||
* Whether to show the cancel button.
|
||||
* @default true (if onCancel is provided)
|
||||
*/
|
||||
showCancel?: boolean
|
||||
/**
|
||||
* Whether the confirm button is in loading state.
|
||||
*/
|
||||
loading?: boolean
|
||||
/**
|
||||
* Whether to close the dialog when pressing the overlay.
|
||||
* @default false
|
||||
*/
|
||||
closeOnOverlayPress?: boolean
|
||||
/**
|
||||
* Style overrides for the content container.
|
||||
*/
|
||||
contentStyle?: StyleProp<ViewStyle>
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple dialog component for confirmations and alerts.
|
||||
* Use this for simple confirm/cancel dialogs. For complex content, use Modal instead.
|
||||
* @param {DialogProps} props - The props for the `Dialog` component.
|
||||
* @returns {JSX.Element} The rendered `Dialog` component.
|
||||
* @example
|
||||
* // Simple alert
|
||||
* <Dialog
|
||||
* visible={showAlert}
|
||||
* onClose={() => setShowAlert(false)}
|
||||
* title="Success"
|
||||
* message="Operation completed successfully."
|
||||
* onConfirm={() => setShowAlert(false)}
|
||||
* />
|
||||
*
|
||||
* @example
|
||||
* // Destructive confirmation
|
||||
* <Dialog
|
||||
* visible={showConfirm}
|
||||
* onClose={() => setShowConfirm(false)}
|
||||
* preset="destructive"
|
||||
* titleTx="common:confirm"
|
||||
* messageTx="session:confirmLogoutMessage"
|
||||
* confirmTx="session:logout"
|
||||
* onConfirm={handleLogout}
|
||||
* onCancel={() => setShowConfirm(false)}
|
||||
* />
|
||||
*/
|
||||
export function Dialog(props: DialogProps) {
|
||||
const {
|
||||
visible,
|
||||
onClose,
|
||||
preset = "default",
|
||||
titleTx,
|
||||
title,
|
||||
titleTxOptions,
|
||||
messageTx,
|
||||
message,
|
||||
messageTxOptions,
|
||||
confirmTx = "common:ok",
|
||||
confirmText,
|
||||
onConfirm,
|
||||
cancelTx = "common:cancel",
|
||||
cancelText,
|
||||
onCancel,
|
||||
showCancel = !!onCancel,
|
||||
loading = false,
|
||||
closeOnOverlayPress = false,
|
||||
contentStyle: $contentStyleOverride,
|
||||
} = props
|
||||
|
||||
const { themed, theme } = useAppTheme()
|
||||
|
||||
const isDestructive = preset === "destructive"
|
||||
|
||||
const handleConfirm = () => {
|
||||
onConfirm?.()
|
||||
}
|
||||
|
||||
const handleCancel = () => {
|
||||
onCancel?.()
|
||||
onClose()
|
||||
}
|
||||
|
||||
const handleOverlayPress = () => {
|
||||
if (closeOnOverlayPress) {
|
||||
onClose()
|
||||
}
|
||||
}
|
||||
|
||||
const $confirmButtonStyle: StyleProp<ViewStyle> = [
|
||||
themed($button),
|
||||
isDestructive && themed($destructiveButton),
|
||||
]
|
||||
|
||||
return (
|
||||
<RNModal
|
||||
visible={visible}
|
||||
transparent
|
||||
animationType="fade"
|
||||
onRequestClose={onClose}
|
||||
statusBarTranslucent
|
||||
>
|
||||
<Pressable style={themed($overlay)} onPress={handleOverlayPress}>
|
||||
<Pressable
|
||||
style={[themed($contentContainer), $contentStyleOverride]}
|
||||
onPress={(e) => e.stopPropagation()}
|
||||
>
|
||||
{/* Title */}
|
||||
{(titleTx || title) && (
|
||||
<Text
|
||||
preset="subheading"
|
||||
tx={titleTx}
|
||||
text={title}
|
||||
txOptions={titleTxOptions}
|
||||
style={themed($title)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Message */}
|
||||
{(messageTx || message) && (
|
||||
<Text
|
||||
tx={messageTx}
|
||||
text={message}
|
||||
txOptions={messageTxOptions}
|
||||
style={themed($message)}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Buttons */}
|
||||
<View style={themed($buttonContainer)}>
|
||||
{showCancel && (
|
||||
<Button
|
||||
preset="default"
|
||||
tx={cancelTx}
|
||||
text={cancelText}
|
||||
style={themed($button)}
|
||||
onPress={handleCancel}
|
||||
disabled={loading}
|
||||
/>
|
||||
)}
|
||||
<Button
|
||||
preset={isDestructive ? "default" : "filled"}
|
||||
tx={confirmTx}
|
||||
text={confirmText}
|
||||
style={$confirmButtonStyle}
|
||||
textStyle={isDestructive ? { color: theme.colors.error } : undefined}
|
||||
onPress={handleConfirm}
|
||||
loading={loading}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
</Pressable>
|
||||
</RNModal>
|
||||
)
|
||||
}
|
||||
|
||||
const $overlay: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({
|
||||
flex: 1,
|
||||
backgroundColor: colors.palette.overlay50,
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
padding: spacing.lg,
|
||||
})
|
||||
|
||||
const $contentContainer: ThemedStyle<ViewStyle> = ({ colors, spacing }) => ({
|
||||
backgroundColor: colors.background,
|
||||
borderRadius: s(12),
|
||||
width: "100%",
|
||||
maxWidth: s(320),
|
||||
paddingHorizontal: spacing.lg,
|
||||
paddingVertical: spacing.lg,
|
||||
})
|
||||
|
||||
const $title: ThemedStyle<TextStyle> = ({ spacing }) => ({
|
||||
textAlign: "center",
|
||||
marginBottom: spacing.sm,
|
||||
})
|
||||
|
||||
const $message: ThemedStyle<TextStyle> = ({ colors, spacing }) => ({
|
||||
textAlign: "center",
|
||||
color: colors.textDim,
|
||||
marginBottom: spacing.lg,
|
||||
})
|
||||
|
||||
const $buttonContainer: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
||||
flexDirection: "row",
|
||||
justifyContent: "center",
|
||||
gap: spacing.sm,
|
||||
})
|
||||
|
||||
const $button: ThemedStyle<ViewStyle> = () => ({
|
||||
flex: 1,
|
||||
minWidth: s(100),
|
||||
})
|
||||
|
||||
const $destructiveButton: ThemedStyle<ViewStyle> = ({ colors }) => ({
|
||||
borderColor: colors.error,
|
||||
})
|
||||
Reference in New Issue
Block a user