// biome-ignore-all lint/style/useExportsLast: the entry type heads the module so the implementation below reads against it import { type ReactNode, useCallback, useState } from 'react'; import { Pressable, type PressableProps, View } from 'react-native'; import { RightLine as ChevronRight } from 'rn-motion-ui-icons/icons/right-line'; import { usePressState } from '../../hooks/use-press-state'; import { cn } from '../../lib/cn'; import { type ItemRowAdornment, type ItemRowSize, type ItemRowVariant, RowLayout, SIZE_SCALE, VARIANT_CLASSES } from './item-row'; /** Pressable's own handler types, so the row can wrap them without restating the event shapes. */ type HoverHandler = NonNullable; export type ActionRowProps = Omit & { /** Row title — primary text, `weight="medium"` foreground. */ title: ReactNode; /** Optional secondary text, rendered below the title in a muted colour. */ description?: ReactNode; /** Maximum title lines. Use 0 to wrap without truncation. @default 1 */ titleNumberOfLines?: number; /** Maximum description lines. Use 0 to wrap without truncation. @default 2 */ descriptionNumberOfLines?: number; /** * Leading adornment. When `{ icon: ... }`, the icon is wrapped in a * `ThemedIcon` tinted `muted-foreground`; any other ReactNode renders as-is. */ leftAdornment?: ItemRowAdornment; /** Center avatars against the full title/description block, or align icons with its first line. */ leftAdornmentAlignment?: 'start' | 'center'; /** * Trailing adornment. When `{ icon: ... }`, the icon is wrapped in a * `ThemedIcon` tinted `muted-foreground`; any other ReactNode renders as-is. * * **Defaults to a `ChevronRight` caret icon.** Pass `undefined` explicitly to * suppress it, or provide your own adornment to replace it. */ rightAdornment?: ItemRowAdornment; /** * Press handler. The row shows hover (`bg-surface-hover`) and press * (`bg-surface-selected`) state overlays automatically when `onPress` or * `href` is provided. */ onPress?: () => void; /** * Row size — controls padding, icon dimensions, and the type ramp. * @default 'md' */ size?: ItemRowSize; /** Visual flavour of the row surface. @default 'default' */ variant?: ItemRowVariant; /** Optional subtle hairline at the bottom. `inset` starts 12px inside the leading edge. */ separator?: 'inset' | 'full'; /** * URL for link-style navigation (React Native Web extension). * When provided alongside or instead of `onPress`, the row shows * hover/press overlays. */ href?: string; }; // Re-export these so consumers can import everything from './action-row' export type { ItemRowAdornment, ItemRowSize, ItemRowVariant } from './item-row'; /** * A two-line pressable list row: leading adornment, title + optional * description, and a trailing chevron (overridable). Part of the * interactive-surface family, so sizing follows the same `'sm' | 'md' | 'lg'` * ramp as {@link MenuItem}, {@link ItemRow}, and {@link IconButton}. * * `ActionRow` is the interactive counterpart to {@link ItemRow}. It wraps the * row in a `Pressable`, shows hover / press overlays * (`bg-surface-hover` / `bg-surface-selected`), and defaults the right * adornment to a `ChevronRight` caret — the conventional signifier for "this * row navigates or performs an action." * * Supports `href` (React Native Web extension) for link-style navigation. * * @example * // Basic pressable row with default chevron: * * * @example * // Override the default chevron with a custom right adornment: * 3} * onPress={signOut} * /> * * @example * // Link row (web): * */ export function ActionRow({ title, description, titleNumberOfLines, descriptionNumberOfLines, leftAdornment, leftAdornmentAlignment, rightAdornment, onPress, disabled = false, size = 'md', variant = 'default', separator, testID, className, onHoverIn, onHoverOut, onPressIn, onPressOut, href, ...props }: ActionRowProps) { const [hovered, setHovered] = useState(false); const { pressed, pressHandlers } = usePressState({ onPressIn, onPressOut }); // The row owns hover state to drive its own hover fill; press is delegated to // usePressState which forwards to the caller's handler when provided. const handleHoverIn = useCallback( (event) => { setHovered(true); onHoverIn?.(event); }, [onHoverIn], ); const handleHoverOut = useCallback( (event) => { setHovered(false); onHoverOut?.(event); }, [onHoverOut], ); const scale = SIZE_SCALE[size]; const hasPress = typeof onPress === 'function' || typeof href === 'string'; // Default right adornment: ChevronRight caret, overridable by caller. const resolvedRightAdornment = rightAdornment === undefined ? { icon: ChevronRight } : rightAdornment; return ( {separator ? ( ) : null} ); }