250 lines
6.7 KiB
TypeScript
250 lines
6.7 KiB
TypeScript
import { Image, ImageProps, ImageStyle, StyleProp, TextStyle, View, ViewStyle } from "react-native"
|
|
import { useTranslation } from "react-i18next"
|
|
|
|
import { useAppTheme } from "@/theme/context"
|
|
import type { ThemedStyle } from "@/theme/types"
|
|
|
|
import { Button, ButtonProps } from "./Button"
|
|
import { Text, TextProps } from "./Text"
|
|
|
|
const sadFace = require("@assets/images/sad-face.png")
|
|
|
|
interface EmptyStateProps {
|
|
/**
|
|
* An optional prop that specifies the text/image set to use for the empty state.
|
|
*/
|
|
preset?: "generic"
|
|
/**
|
|
* Style override for the container.
|
|
*/
|
|
style?: StyleProp<ViewStyle>
|
|
/**
|
|
* An Image source to be displayed above the heading.
|
|
*/
|
|
imageSource?: ImageProps["source"]
|
|
/**
|
|
* Style overrides for image.
|
|
*/
|
|
imageStyle?: StyleProp<ImageStyle>
|
|
/**
|
|
* Pass any additional props directly to the Image component.
|
|
*/
|
|
ImageProps?: Omit<ImageProps, "source">
|
|
/**
|
|
* The heading text to display if not using `headingTx`.
|
|
*/
|
|
heading?: TextProps["text"]
|
|
/**
|
|
* Heading text which is looked up via i18n.
|
|
*/
|
|
headingTx?: TextProps["tx"]
|
|
/**
|
|
* Optional heading options to pass to i18n. Useful for interpolation
|
|
* as well as explicitly setting locale or translation fallbacks.
|
|
*/
|
|
headingTxOptions?: TextProps["txOptions"]
|
|
/**
|
|
* Style overrides for heading text.
|
|
*/
|
|
headingStyle?: StyleProp<TextStyle>
|
|
/**
|
|
* Pass any additional props directly to the heading Text component.
|
|
*/
|
|
HeadingTextProps?: TextProps
|
|
/**
|
|
* The content text to display if not using `contentTx`.
|
|
*/
|
|
content?: TextProps["text"]
|
|
/**
|
|
* Content text which is looked up via i18n.
|
|
*/
|
|
contentTx?: TextProps["tx"]
|
|
/**
|
|
* Optional content options to pass to i18n. Useful for interpolation
|
|
* as well as explicitly setting locale or translation fallbacks.
|
|
*/
|
|
contentTxOptions?: TextProps["txOptions"]
|
|
/**
|
|
* Style overrides for content text.
|
|
*/
|
|
contentStyle?: StyleProp<TextStyle>
|
|
/**
|
|
* Pass any additional props directly to the content Text component.
|
|
*/
|
|
ContentTextProps?: TextProps
|
|
/**
|
|
* The button text to display if not using `buttonTx`.
|
|
*/
|
|
button?: TextProps["text"]
|
|
/**
|
|
* Button text which is looked up via i18n.
|
|
*/
|
|
buttonTx?: TextProps["tx"]
|
|
/**
|
|
* Optional button options to pass to i18n. Useful for interpolation
|
|
* as well as explicitly setting locale or translation fallbacks.
|
|
*/
|
|
buttonTxOptions?: TextProps["txOptions"]
|
|
/**
|
|
* Style overrides for button.
|
|
*/
|
|
buttonStyle?: ButtonProps["style"]
|
|
/**
|
|
* Style overrides for button text.
|
|
*/
|
|
buttonTextStyle?: ButtonProps["textStyle"]
|
|
/**
|
|
* Called when the button is pressed.
|
|
*/
|
|
buttonOnPress?: ButtonProps["onPress"]
|
|
/**
|
|
* Pass any additional props directly to the Button component.
|
|
*/
|
|
ButtonProps?: ButtonProps
|
|
}
|
|
|
|
interface EmptyStatePresetItem {
|
|
imageSource: ImageProps["source"]
|
|
heading: TextProps["text"]
|
|
content: TextProps["text"]
|
|
button: TextProps["text"]
|
|
}
|
|
|
|
/**
|
|
* A component to use when there is no data to display. It can be utilized to direct the user what to do next.
|
|
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/EmptyState/}
|
|
* @param {EmptyStateProps} props - The props for the `EmptyState` component.
|
|
* @returns {JSX.Element} The rendered `EmptyState` component.
|
|
*/
|
|
export function EmptyState(props: EmptyStateProps) {
|
|
const {
|
|
theme,
|
|
themed,
|
|
theme: { spacing },
|
|
} = useAppTheme()
|
|
const { t } = useTranslation()
|
|
|
|
const EmptyStatePresets = {
|
|
generic: {
|
|
imageSource: sadFace,
|
|
heading: t("emptyStateComponent:generic.heading"),
|
|
content: t("emptyStateComponent:generic.content"),
|
|
button: t("emptyStateComponent:generic.button"),
|
|
} as EmptyStatePresetItem,
|
|
} as const
|
|
|
|
const preset = EmptyStatePresets[props.preset ?? "generic"]
|
|
|
|
const {
|
|
button = preset.button,
|
|
buttonTx,
|
|
buttonOnPress,
|
|
buttonTxOptions,
|
|
content = preset.content,
|
|
contentTx,
|
|
contentTxOptions,
|
|
heading = preset.heading,
|
|
headingTx,
|
|
headingTxOptions,
|
|
imageSource = preset.imageSource,
|
|
style: $containerStyleOverride,
|
|
buttonStyle: $buttonStyleOverride,
|
|
buttonTextStyle: $buttonTextStyleOverride,
|
|
contentStyle: $contentStyleOverride,
|
|
headingStyle: $headingStyleOverride,
|
|
imageStyle: $imageStyleOverride,
|
|
ButtonProps,
|
|
ContentTextProps,
|
|
HeadingTextProps,
|
|
ImageProps,
|
|
} = props
|
|
|
|
const isImagePresent = !!imageSource
|
|
const isHeadingPresent = !!(heading || headingTx)
|
|
const isContentPresent = !!(content || contentTx)
|
|
const isButtonPresent = !!(button || buttonTx)
|
|
|
|
const $containerStyles = [$containerStyleOverride]
|
|
const $imageStyles = [
|
|
$image,
|
|
(isHeadingPresent || isContentPresent || isButtonPresent) && { marginBottom: spacing.xxxs },
|
|
$imageStyleOverride,
|
|
ImageProps?.style,
|
|
]
|
|
const $headingStyles = [
|
|
themed($heading),
|
|
isImagePresent && { marginTop: spacing.xxxs },
|
|
(isContentPresent || isButtonPresent) && { marginBottom: spacing.xxxs },
|
|
$headingStyleOverride,
|
|
HeadingTextProps?.style,
|
|
]
|
|
const $contentStyles = [
|
|
themed($content),
|
|
(isImagePresent || isHeadingPresent) && { marginTop: spacing.xxxs },
|
|
isButtonPresent && { marginBottom: spacing.xxxs },
|
|
$contentStyleOverride,
|
|
ContentTextProps?.style,
|
|
]
|
|
const $buttonStyles = [
|
|
(isImagePresent || isHeadingPresent || isContentPresent) && { marginTop: spacing.xl },
|
|
$buttonStyleOverride,
|
|
ButtonProps?.style,
|
|
]
|
|
|
|
return (
|
|
<View style={$containerStyles}>
|
|
{isImagePresent && (
|
|
<Image
|
|
source={imageSource}
|
|
{...ImageProps}
|
|
style={$imageStyles}
|
|
tintColor={theme.colors.palette.neutral900}
|
|
/>
|
|
)}
|
|
|
|
{isHeadingPresent && (
|
|
<Text
|
|
preset="subheading"
|
|
text={heading}
|
|
tx={headingTx}
|
|
txOptions={headingTxOptions}
|
|
{...HeadingTextProps}
|
|
style={$headingStyles}
|
|
/>
|
|
)}
|
|
|
|
{isContentPresent && (
|
|
<Text
|
|
text={content}
|
|
tx={contentTx}
|
|
txOptions={contentTxOptions}
|
|
{...ContentTextProps}
|
|
style={$contentStyles}
|
|
/>
|
|
)}
|
|
|
|
{isButtonPresent && (
|
|
<Button
|
|
onPress={buttonOnPress}
|
|
text={button}
|
|
tx={buttonTx}
|
|
txOptions={buttonTxOptions}
|
|
textStyle={$buttonTextStyleOverride}
|
|
{...ButtonProps}
|
|
style={$buttonStyles}
|
|
/>
|
|
)}
|
|
</View>
|
|
)
|
|
}
|
|
|
|
const $image: ImageStyle = { alignSelf: "center" }
|
|
const $heading: ThemedStyle<TextStyle> = ({ spacing }) => ({
|
|
textAlign: "center",
|
|
paddingHorizontal: spacing.lg,
|
|
})
|
|
const $content: ThemedStyle<TextStyle> = ({ spacing }) => ({
|
|
textAlign: "center",
|
|
paddingHorizontal: spacing.lg,
|
|
})
|