import { isValidElement } from 'react'; import { getWebProps } from 'react-native-unistyles/web'; import { useUnistyles } from 'react-native-unistyles'; import { getColorFromString, Intent, Theme, Color } from '@idealyst/theme'; import { listStyles } from './List.styles'; import type { ListItemProps } from './types'; import { IconSvg } from '../Icon/IconSvg/IconSvg.web'; import { isIconName } from '../Icon/icon-resolver'; import { useListContext } from './ListContext'; const ListItem: React.FC = ({ id: _id, label, children, leading, trailing, iconColor, active = false, selected = false, disabled = false, indent = 0, size, onPress, style, testID, isLast = false, }) => { const { theme } = useUnistyles() as { theme: Theme }; const listContext = useListContext(); const isClickable = !disabled && !!onPress; // Use explicit size prop, fallback to context size, then default const effectiveSize = size ?? listContext.size ?? 'md'; const effectiveVariant = listContext.type ?? 'default'; // Apply variants (for size, active, selected, disabled on label) listStyles.useVariants({ size: effectiveSize, active, selected, disabled, }); // Get dynamic styles - call as functions for theme reactivity const itemStyle = (listStyles.item as any)({ type: effectiveVariant, disabled, clickable: isClickable, isLast }); const labelStyle = (listStyles.label as any)({ disabled, selected }); const leadingStyle = (listStyles.leading as any)({}); const trailingStyle = (listStyles.trailing as any)({}); const labelContainerStyle = (listStyles.labelContainer as any)({}); const itemProps = getWebProps([itemStyle, style]); const labelProps = getWebProps([labelStyle]); const leadingProps = getWebProps([leadingStyle]); const trailingProps = getWebProps([trailingStyle]); const handleClick = (e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); if (!disabled && onPress) { onPress(); } }; // Resolve icon color - check intents first, then color palette const resolvedIconColor = (() => { if (!iconColor) return undefined; // Check if it's an intent name if (iconColor in theme.intents) { return theme.intents[iconColor as Intent]?.primary; } // Otherwise try color palette return getColorFromString(theme, iconColor as Color); })(); // Helper to render leading/trailing icons // IconSvg uses size="1em" by default, sized by container's fontSize from styles const renderElement = (element: typeof leading | typeof trailing) => { if (!element) return null; if (isIconName(element)) { // Use IconSvg with name - registry lookup happens inside return ( ); } else if (isValidElement(element)) { return element; } return null; }; const labelContainerProps = getWebProps([labelContainerStyle]); const content = ( <> {leading && ( {renderElement(leading)} )}
{label && ( {label} )} {children}
{trailing && (
{renderElement(trailing)}
)} ); const indentStyle = indent > 0 ? { paddingLeft: `${indent * 16}px` } : {}; return (
{content}
); }; export default ListItem;