template_0205
This commit is contained in:
257
RN_TEMPLATE/app/components/ListItem.tsx
Normal file
257
RN_TEMPLATE/app/components/ListItem.tsx
Normal file
@@ -0,0 +1,257 @@
|
||||
import { forwardRef, ReactElement } from "react"
|
||||
import {
|
||||
StyleProp,
|
||||
TextStyle,
|
||||
TouchableOpacity,
|
||||
TouchableOpacityProps,
|
||||
View,
|
||||
ViewStyle,
|
||||
} from "react-native"
|
||||
|
||||
import { useAppTheme } from "@/theme/context"
|
||||
import { $styles } from "@/theme/styles"
|
||||
import type { ThemedStyle } from "@/theme/types"
|
||||
import { s } from "@/utils/responsive"
|
||||
|
||||
import { Icon, IconTypes } from "./Icon"
|
||||
import { Text, TextProps } from "./Text"
|
||||
|
||||
export interface ListItemProps extends TouchableOpacityProps {
|
||||
/**
|
||||
* How tall the list item should be.
|
||||
* Default: 56
|
||||
*/
|
||||
height?: number
|
||||
/**
|
||||
* Whether to show the top separator.
|
||||
* Default: false
|
||||
*/
|
||||
topSeparator?: boolean
|
||||
/**
|
||||
* Whether to show the bottom separator.
|
||||
* Default: false
|
||||
*/
|
||||
bottomSeparator?: boolean
|
||||
/**
|
||||
* Text to display if not using `tx` or nested components.
|
||||
*/
|
||||
text?: TextProps["text"]
|
||||
/**
|
||||
* Text which is looked up via i18n.
|
||||
*/
|
||||
tx?: TextProps["tx"]
|
||||
/**
|
||||
* Children components.
|
||||
*/
|
||||
children?: TextProps["children"]
|
||||
/**
|
||||
* Optional options to pass to i18n. Useful for interpolation
|
||||
* as well as explicitly setting locale or translation fallbacks.
|
||||
*/
|
||||
txOptions?: TextProps["txOptions"]
|
||||
/**
|
||||
* Optional text style override.
|
||||
*/
|
||||
textStyle?: StyleProp<TextStyle>
|
||||
/**
|
||||
* Pass any additional props directly to the Text component.
|
||||
*/
|
||||
TextProps?: TextProps
|
||||
/**
|
||||
* Optional View container style override.
|
||||
*/
|
||||
containerStyle?: StyleProp<ViewStyle>
|
||||
/**
|
||||
* Optional TouchableOpacity style override.
|
||||
*/
|
||||
style?: StyleProp<ViewStyle>
|
||||
/**
|
||||
* Icon that should appear on the left.
|
||||
*/
|
||||
leftIcon?: IconTypes
|
||||
/**
|
||||
* An optional tint color for the left icon
|
||||
*/
|
||||
leftIconColor?: string
|
||||
/**
|
||||
* Icon that should appear on the right.
|
||||
*/
|
||||
rightIcon?: IconTypes
|
||||
/**
|
||||
* An optional tint color for the right icon
|
||||
*/
|
||||
rightIconColor?: string
|
||||
/**
|
||||
* Right action custom ReactElement.
|
||||
* Overrides `rightIcon`.
|
||||
*/
|
||||
RightComponent?: ReactElement
|
||||
/**
|
||||
* Left action custom ReactElement.
|
||||
* Overrides `leftIcon`.
|
||||
*/
|
||||
LeftComponent?: ReactElement
|
||||
}
|
||||
|
||||
interface ListItemActionProps {
|
||||
icon?: IconTypes
|
||||
iconColor?: string
|
||||
Component?: ReactElement
|
||||
size: number
|
||||
side: "left" | "right"
|
||||
}
|
||||
|
||||
/**
|
||||
* A styled row component that can be used in FlatList, SectionList, or by itself.
|
||||
* @see [Documentation and Examples]{@link https://docs.infinite.red/ignite-cli/boilerplate/app/components/ListItem/}
|
||||
* @param {ListItemProps} props - The props for the `ListItem` component.
|
||||
* @returns {JSX.Element} The rendered `ListItem` component.
|
||||
*/
|
||||
export const ListItem = forwardRef<View, ListItemProps>(function ListItem(
|
||||
props: ListItemProps,
|
||||
ref,
|
||||
) {
|
||||
const {
|
||||
bottomSeparator,
|
||||
children,
|
||||
height = s(56),
|
||||
LeftComponent,
|
||||
leftIcon,
|
||||
leftIconColor,
|
||||
RightComponent,
|
||||
rightIcon,
|
||||
rightIconColor,
|
||||
style,
|
||||
text,
|
||||
TextProps,
|
||||
topSeparator,
|
||||
tx,
|
||||
txOptions,
|
||||
textStyle: $textStyleOverride,
|
||||
containerStyle: $containerStyleOverride,
|
||||
...TouchableOpacityProps
|
||||
} = props
|
||||
const { themed } = useAppTheme()
|
||||
|
||||
const isTouchable =
|
||||
TouchableOpacityProps.onPress !== undefined ||
|
||||
TouchableOpacityProps.onPressIn !== undefined ||
|
||||
TouchableOpacityProps.onPressOut !== undefined ||
|
||||
TouchableOpacityProps.onLongPress !== undefined
|
||||
|
||||
const $textStyles = [$textStyle, $textStyleOverride, TextProps?.style]
|
||||
|
||||
const $containerStyles = [
|
||||
topSeparator && $separatorTop,
|
||||
bottomSeparator && $separatorBottom,
|
||||
$containerStyleOverride,
|
||||
]
|
||||
|
||||
const $touchableStyles = [$styles.row, $touchableStyle, { minHeight: height }, style]
|
||||
|
||||
const Wrapper = isTouchable ? TouchableOpacity : View
|
||||
|
||||
return (
|
||||
<View ref={ref} style={themed($containerStyles)}>
|
||||
<Wrapper {...TouchableOpacityProps} style={$touchableStyles}>
|
||||
<ListItemAction
|
||||
side="left"
|
||||
size={height}
|
||||
icon={leftIcon}
|
||||
iconColor={leftIconColor}
|
||||
Component={LeftComponent}
|
||||
/>
|
||||
|
||||
<Text {...TextProps} tx={tx} text={text} txOptions={txOptions} style={themed($textStyles)}>
|
||||
{children}
|
||||
</Text>
|
||||
|
||||
<ListItemAction
|
||||
side="right"
|
||||
size={height}
|
||||
icon={rightIcon}
|
||||
iconColor={rightIconColor}
|
||||
Component={RightComponent}
|
||||
/>
|
||||
</Wrapper>
|
||||
</View>
|
||||
)
|
||||
})
|
||||
|
||||
/**
|
||||
* @param {ListItemActionProps} props - The props for the `ListItemAction` component.
|
||||
* @returns {JSX.Element | null} The rendered `ListItemAction` component.
|
||||
*/
|
||||
function ListItemAction(props: ListItemActionProps) {
|
||||
const { icon, Component, iconColor, size, side } = props
|
||||
const { themed } = useAppTheme()
|
||||
|
||||
const $iconContainerStyles = [$iconContainer]
|
||||
|
||||
if (Component) {
|
||||
return (
|
||||
<View
|
||||
style={themed([
|
||||
$iconContainerStyles,
|
||||
side === "left" && $iconContainerLeft,
|
||||
side === "right" && $iconContainerRight,
|
||||
{ height: size },
|
||||
])}
|
||||
>
|
||||
{Component}
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
if (icon !== undefined) {
|
||||
return (
|
||||
<Icon
|
||||
size={s(24)}
|
||||
icon={icon}
|
||||
color={iconColor}
|
||||
containerStyle={themed([
|
||||
$iconContainerStyles,
|
||||
side === "left" && $iconContainerLeft,
|
||||
side === "right" && $iconContainerRight,
|
||||
{ height: size },
|
||||
])}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
const $separatorTop: ThemedStyle<ViewStyle> = ({ colors }) => ({
|
||||
borderTopWidth: 1,
|
||||
borderTopColor: colors.separator,
|
||||
})
|
||||
|
||||
const $separatorBottom: ThemedStyle<ViewStyle> = ({ colors }) => ({
|
||||
borderBottomWidth: 1,
|
||||
borderBottomColor: colors.separator,
|
||||
})
|
||||
|
||||
const $textStyle: ThemedStyle<TextStyle> = ({ spacing }) => ({
|
||||
paddingVertical: spacing.xs,
|
||||
alignSelf: "center",
|
||||
flexGrow: 1,
|
||||
flexShrink: 1,
|
||||
})
|
||||
|
||||
const $touchableStyle: ViewStyle = {
|
||||
alignItems: "flex-start",
|
||||
}
|
||||
|
||||
const $iconContainer: ViewStyle = {
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
flexGrow: 0,
|
||||
}
|
||||
const $iconContainerLeft: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
||||
marginEnd: spacing.md,
|
||||
})
|
||||
|
||||
const $iconContainerRight: ThemedStyle<ViewStyle> = ({ spacing }) => ({
|
||||
marginStart: spacing.md,
|
||||
})
|
||||
Reference in New Issue
Block a user