Item
Generic media / content / actions row. Sources, file-tree, attachments, model-selector, queue, plan and task all render lists of these.
- v0.2.1
- ui
- nativewind
- uniwind
- runs in Expo Go
Install
npx @react-native-reusables/cli@latest add https://rnraielements.com/r/nativewind/item.jsonnpx @react-native-reusables/cli@latest add https://rnraielements.com/r/uniwind/item.jsonDependencies
class-variance-authority
https://reactnativereusables.com/r/nativewind/text.json |
https://reactnativereusables.com/r/nativewind/separator.json |
https://reactnativereusables.com/r/uniwind/text.json |
https://reactnativereusables.com/r/uniwind/separator.json |
AI SDK peers:ai@^7.0.89@ai-sdk/react@^4.0.92
Source
import { Text, TextClassContext } from '@/registry/nativewind/components/ui/text';
import { cn } from '@/registry/nativewind/lib/utils';
import { cva, type VariantProps } from 'class-variance-authority';
import { Platform, Pressable, View, type ViewProps } from 'react-native';
/**
* Item — the generic media / content / actions row.
*
* RNR ships no `item`. Second-highest leverage of the gap after input-group: sources,
* file-tree, attachments, model-selector, queue, plan and task all render lists of these.
*
* Publishes TextClassContext so ItemTitle and ItemDescription style their descendants
* without per-call classes — React Native has no cascade, so a className on this View
* would never reach a nested Text.
*/
const itemVariants = cva('flex-row items-center gap-3 rounded-md px-3 py-2', {
variants: {
variant: {
default: '',
outline: 'border border-border',
muted: 'bg-muted/50',
},
// Web keeps its hover; native gets the active twin. A hover-only row renders
// identically at rest and is dead under a thumb.
interactive: { true: 'active:bg-accent', false: '' },
},
defaultVariants: { variant: 'default', interactive: false },
});
type ItemProps = ViewProps &
VariantProps<typeof itemVariants> & {
onPress?: () => void;
};
function Item({ className, variant, onPress, ...props }: ItemProps) {
const interactive = Boolean(onPress);
const classes = cn(
itemVariants({ variant, interactive }),
interactive && Platform.select({ web: 'hover:bg-accent cursor-pointer' }),
className,
);
if (interactive) {
return (
<Pressable
className={classes}
onPress={onPress}
accessibilityRole="button"
// RNR's mobile control is h-10 (40pt). hitSlop reaches the 44pt platform minimum
// with a zero-pixel visual change; growing to h-11 would make our rows taller
// than every other row in the host app.
hitSlop={{ top: 2, bottom: 2, left: 2, right: 2 }}
{...props}
/>
);
}
return <View className={classes} {...props} />;
}
function ItemMedia({ className, ...props }: ViewProps) {
return <View className={cn('shrink-0 items-center justify-center', className)} {...props} />;
}
function ItemContent({ className, ...props }: ViewProps) {
return <View className={cn('min-w-0 flex-1 gap-0.5', className)} {...props} />;
}
function ItemTitle({ className, ...props }: React.ComponentProps<typeof Text>) {
return (
<Text numberOfLines={1} className={cn('text-sm font-medium text-foreground', className)} {...props} />
);
}
function ItemDescription({ className, ...props }: React.ComponentProps<typeof Text>) {
return (
<Text numberOfLines={2} className={cn('text-xs text-muted-foreground', className)} {...props} />
);
}
function ItemActions({ className, ...props }: ViewProps) {
return (
<TextClassContext.Provider value="text-xs font-medium">
<View className={cn('shrink-0 flex-row items-center gap-1', className)} {...props} />
</TextClassContext.Provider>
);
}
export { Item, ItemActions, ItemContent, ItemDescription, ItemMedia, ItemTitle, itemVariants };
Theming
Themed via border-border, bg-muted, bg-accent, text-foreground, text-muted-foreground.
Usage
() => (
<Item onPress={() => {}}>
<ItemContent>
<ItemTitle>
<Text className="text-sm font-medium text-foreground">conversation.json</Text>
</ItemTitle>
<ItemDescription>
<Text className="text-xs text-muted-foreground">
Virtualized transcript · uniwind · v0.2.0
</Text>
</ItemDescription>
</ItemContent>
</Item>
)