template_0205

This commit is contained in:
Sofio
2026-02-05 13:16:05 +08:00
commit d93e4d9c9f
197 changed files with 52810 additions and 0 deletions

View 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,
})