BTDeviceItem
A compound component for rendering individual medical device cards with customizable layout, status indicators, and interactive elements.
Overview
The BTDeviceItem component follows a compound component pattern, providing maximum flexibility for creating custom device card layouts. It includes multiple sub-components that can be composed together to create rich, interactive device interfaces.
Compound Component Architecture
BTDeviceItem // Main container (TouchableOpacity)
├── BTDeviceItem.TopContainer // Top section layout
│ ├── BTDeviceItem.Content // Text content area
│ │ ├── BTDeviceItem.Title // Device name
│ │ ├── BTDeviceItem.SubTitle // Device description
│ │ └── BTDeviceItem.Status // Connection status
│ └── BTDeviceItem.Image // Device image
├── BTDeviceItem.Divider // Separator line
└── BTDeviceItem.BottomContainer // Bottom section layout
Component API
BTDeviceItem (Main Container)
interface BTDeviceItemProps extends TouchableOpacityProps {
children: React.ReactNode;
}
The main container is a TouchableOpacity with theme-aware styling including shadows, borders, and background colors.
Sub-Components
| Component | Base | Props | Description |
|---|---|---|---|
TopContainer | View | ViewProps | Top section with horizontal layout |
Content | View | ViewProps | Text content area with vertical layout |
Title | Text | TextProps<T> | Primary device title with theme colors |
SubTitle | Text | TextProps<T> | Secondary device description |
Status | Text | BTDeviceItemStatusProps<T> | Status with color coding |
Image | OptimizedImage | OptimizedImageProps | Device image (65x65) |
Divider | Divider | Paper Divider | Horizontal separator line |
BottomContainer | View | ViewProps | Bottom section with horizontal layout |
Basic Usage
Simple Device Card
import React from "react";
import { BTDeviceItem } from "@modules/bt-device";
import { DeviceStatus } from "@modules/bt-management";
const SimpleDeviceCard = () => {
return (
<BTDeviceItem onPress={() => console.log("Device pressed")}>
<BTDeviceItem.TopContainer>
<BTDeviceItem.Content>
<BTDeviceItem.Title>Blood Pressure Monitor</BTDeviceItem.Title>
<BTDeviceItem.SubTitle>Wellue BP2 Armfit+</BTDeviceItem.SubTitle>
<BTDeviceItem.Status status={DeviceStatus.CONNECTED} />
</BTDeviceItem.Content>
<BTDeviceItem.Image uri="device-image-uri" />
</BTDeviceItem.TopContainer>
</BTDeviceItem>
);
};
Complete Device Card
import React from "react";
import { ArrowRight2 } from "iconsax-react-nativejs";
import { BTDeviceItem } from "@modules/bt-device";
import { useAppTheme } from "@modules/theme";
const CompleteDeviceCard = ({ device, onPress, onInstructionsPress }) => {
const theme = useAppTheme();
return (
<BTDeviceItem
onPress={() => onPress(device)}
accessibilityLabel={`${device.title} - ${device.status}`}
>
<BTDeviceItem.TopContainer>
<BTDeviceItem.Content>
<BTDeviceItem.Title>{device.title}</BTDeviceItem.Title>
<BTDeviceItem.SubTitle>{device.subtitle}</BTDeviceItem.SubTitle>
<BTDeviceItem.Status status={device.status} />
</BTDeviceItem.Content>
<BTDeviceItem.Image uri={device.deviceImageUri} />
</BTDeviceItem.TopContainer>
<BTDeviceItem.Divider />
<BTDeviceItem.BottomContainer>
<BTDeviceItem.SubTitle>View Instructions</BTDeviceItem.SubTitle>
<ArrowRight2 size={20} color={theme.colors.onSurfaceDisabled} />
</BTDeviceItem.BottomContainer>
</BTDeviceItem>
);
};
Advanced Usage
Custom Layout Variations
import React from "react";
import { View } from "react-native";
import { BTDeviceItem } from "@modules/bt-device";
// Minimal card
const MinimalDeviceCard = ({ device }) => (
<BTDeviceItem onPress={() => console.log("Pressed")}>
<BTDeviceItem.TopContainer>
<BTDeviceItem.Content>
<BTDeviceItem.Title>{device.title}</BTDeviceItem.Title>
<BTDeviceItem.Status status={device.status} />
</BTDeviceItem.Content>
</BTDeviceItem.TopContainer>
</BTDeviceItem>
);
// Image-focused card
const ImageFocusedCard = ({ device }) => (
<BTDeviceItem onPress={() => console.log("Pressed")}>
<View style={{ alignItems: "center", padding: 16 }}>
<BTDeviceItem.Image uri={device.deviceImageUri} />
<BTDeviceItem.Title style={{ marginTop: 8 }}>
{device.title}
</BTDeviceItem.Title>
<BTDeviceItem.Status status={device.status} />
</View>
</BTDeviceItem>
);
// Horizontal layout
const HorizontalDeviceCard = ({ device }) => (
<BTDeviceItem onPress={() => console.log("Pressed")}>
<BTDeviceItem.TopContainer style={{ alignItems: "center" }}>
<BTDeviceItem.Image uri={device.deviceImageUri} />
<BTDeviceItem.Content style={{ marginLeft: 12 }}>
<BTDeviceItem.Title>{device.title}</BTDeviceItem.Title>
<BTDeviceItem.SubTitle>{device.subtitle}</BTDeviceItem.SubTitle>
</BTDeviceItem.Content>
<BTDeviceItem.Status status={device.status} />
</BTDeviceItem.TopContainer>
</BTDeviceItem>
);
Status Indicator Customization
import React from "react";
import { View } from "react-native";
import { BTDeviceItem } from "@modules/bt-device";
import { DeviceStatus } from "@modules/bt-management";
const StatusIndicatorCard = ({ device }) => {
const getStatusIcon = (status: DeviceStatus) => {
switch (status) {
case DeviceStatus.CONNECTED:
return "🟢";
case DeviceStatus.MEASURING:
return "🟡";
case DeviceStatus.LOW_BATTERY:
return "🔴";
default:
return "⚪";
}
};
return (
<BTDeviceItem onPress={() => console.log("Pressed")}>
<BTDeviceItem.TopContainer>
<BTDeviceItem.Content>
<View style={{ flexDirection: "row", alignItems: "center" }}>
<BTDeviceItem.Title>{device.title}</BTDeviceItem.Title>
<Text style={{ marginLeft: 8, fontSize: 16 }}>
{getStatusIcon(device.status)}
</Text>
</View>
<BTDeviceItem.SubTitle>{device.subtitle}</BTDeviceItem.SubTitle>
<BTDeviceItem.Status status={device.status} />
</BTDeviceItem.Content>
<BTDeviceItem.Image uri={device.deviceImageUri} />
</BTDeviceItem.TopContainer>
</BTDeviceItem>
);
};
Interactive Elements
import React from "react";
import { Button, View } from "react-native";
import { BTDeviceItem } from "@modules/bt-device";
const InteractiveDeviceCard = ({
device,
onConnect,
onDisconnect,
onInstructions,
}) => {
const isConnected = device.status === DeviceStatus.CONNECTED;
return (
<BTDeviceItem>
<BTDeviceItem.TopContainer>
<BTDeviceItem.Content>
<BTDeviceItem.Title>{device.title}</BTDeviceItem.Title>
<BTDeviceItem.SubTitle>{device.subtitle}</BTDeviceItem.SubTitle>
<BTDeviceItem.Status status={device.status} />
</BTDeviceItem.Content>
<BTDeviceItem.Image uri={device.deviceImageUri} />
</BTDeviceItem.TopContainer>
<BTDeviceItem.Divider />
<View style={{ padding: 16, gap: 12 }}>
<Button
title={isConnected ? "Disconnect" : "Connect"}
onPress={isConnected ? onDisconnect : onConnect}
/>
<Button
title="View Instructions"
onPress={onInstructions}
variant="outline"
/>
</View>
</BTDeviceItem>
);
};
Sub-Component Details
BTDeviceItem.Status
Special component for displaying device connection status with color coding.
interface BTDeviceItemStatusProps<T extends MD3TypescaleKey> {
status: DeviceStatus;
} & Omit<TextProps<T>, "children">;
Status Color Mapping:
| Status | Color | Label |
|---|---|---|
CONNECTED | theme.colors.success | "Connected" |
DISCONNECTED | theme.colors.error | "Disconnected" |
MEASURING | theme.colors.warning | "Measuring" |
LOW_BATTERY | theme.colors.error | "Low Battery" |
// Usage with custom styling
<BTDeviceItem.Status
status={DeviceStatus.CONNECTED}
style={{ fontSize: 16, fontWeight: "bold" }}
/>
BTDeviceItem.Image
Optimized image component with fixed dimensions for consistent layout.
interface OptimizedImageProps {
uri: string;
width?: number; // Default: 65
height?: number; // Default: 65
style?: ImageStyle;
}
// Custom sized image
<BTDeviceItem.Image
uri={device.deviceImageUri}
width={80}
height={80}
style={{ borderRadius: 8 }}
/>
BTDeviceItem.Title and SubTitle
Text components with theme-aware styling.
// Title with custom styling
<BTDeviceItem.Title style={{ fontSize: 20, fontWeight: '800' }}>
Custom Device Name
</BTDeviceItem.Title>
// SubTitle with secondary color
<BTDeviceItem.SubTitle style={{ color: theme.colors.onSurfaceVariant }}>
Device Model Number
</BTDeviceItem.SubTitle>
Styling and Theming
Theme Integration
All components automatically use the app theme for consistent styling:
// Main container styling (automatic)
{
borderRadius: theme.borderRadius(4),
shadowColor: theme.colors.shadow,
backgroundColor: theme.colors.surface,
borderColor: theme.colors.outlineVariant,
// ... shadow and elevation styles
}
// Title styling (automatic)
{
fontSize: 18,
fontFamily: "DMSans_700Bold",
color: theme.colors.primary
}
// SubTitle styling (automatic)
{
fontSize: 14,
fontFamily: "DMSans_500Medium"
}
Custom Styling
import { StyleSheet } from "react-native";
import { useAppTheme } from "@modules/theme";
const CustomStyledCard = () => {
const theme = useAppTheme();
return (
<BTDeviceItem style={styles.customCard}>
<BTDeviceItem.TopContainer style={styles.topSection}>
<BTDeviceItem.Content style={styles.contentArea}>
<BTDeviceItem.Title style={styles.customTitle}>
Custom Device
</BTDeviceItem.Title>
<BTDeviceItem.SubTitle style={styles.customSubtitle}>
With custom styling
</BTDeviceItem.SubTitle>
</BTDeviceItem.Content>
</BTDeviceItem.TopContainer>
</BTDeviceItem>
);
};
const styles = StyleSheet.create({
customCard: {
borderRadius: 16,
marginHorizontal: 8,
backgroundColor: "#f8f9fa",
},
topSection: {
padding: 20,
},
contentArea: {
alignItems: "center",
},
customTitle: {
fontSize: 22,
color: "#2c3e50",
},
customSubtitle: {
fontSize: 16,
color: "#7f8c8d",
marginTop: 4,
},
});
Accessibility
Screen Reader Support
<BTDeviceItem
accessible={true}
accessibilityRole="button"
accessibilityLabel={`${device.title}, ${device.subtitle}, Status: ${device.status}`}
accessibilityHint="Tap to connect to this device"
onPress={handlePress}
>
{/* Content */}
</BTDeviceItem>
Accessibility Best Practices
const AccessibleDeviceCard = ({ device }) => {
return (
<BTDeviceItem
accessible={true}
accessibilityRole="button"
accessibilityState={{
selected: device.status === DeviceStatus.CONNECTED,
}}
accessibilityValue={{
text: `Connection status: ${device.status}`,
}}
>
<BTDeviceItem.TopContainer>
<BTDeviceItem.Content>
<BTDeviceItem.Title
accessible={false} // Grouped with parent
>
{device.title}
</BTDeviceItem.Title>
<BTDeviceItem.SubTitle
accessible={false} // Grouped with parent
>
{device.subtitle}
</BTDeviceItem.SubTitle>
<BTDeviceItem.Status
status={device.status}
accessible={false} // Grouped with parent
/>
</BTDeviceItem.Content>
<BTDeviceItem.Image
uri={device.deviceImageUri}
accessible={false} // Decorative image
/>
</BTDeviceItem.TopContainer>
</BTDeviceItem>
);
};
Animation and Interactions
Press Animations
import { Animated } from "react-native";
const AnimatedDeviceCard = ({ device }) => {
const scaleValue = useRef(new Animated.Value(1)).current;
const handlePressIn = () => {
Animated.spring(scaleValue, {
toValue: 0.95,
useNativeDriver: true,
}).start();
};
const handlePressOut = () => {
Animated.spring(scaleValue, {
toValue: 1,
useNativeDriver: true,
}).start();
};
return (
<Animated.View style={{ transform: [{ scale: scaleValue }] }}>
<BTDeviceItem
onPressIn={handlePressIn}
onPressOut={handlePressOut}
onPress={() => console.log("Pressed")}
>
{/* Content */}
</BTDeviceItem>
</Animated.View>
);
};
Status Animations
import { useEffect, useRef } from "react";
import { Animated } from "react-native";
const AnimatedStatusCard = ({ device }) => {
const pulseValue = useRef(new Animated.Value(1)).current;
useEffect(() => {
if (device.status === DeviceStatus.MEASURING) {
const pulse = Animated.loop(
Animated.sequence([
Animated.timing(pulseValue, {
toValue: 1.1,
duration: 800,
useNativeDriver: true,
}),
Animated.timing(pulseValue, {
toValue: 1,
duration: 800,
useNativeDriver: true,
}),
]),
);
pulse.start();
return () => pulse.stop();
}
}, [device.status]);
return (
<BTDeviceItem>
<BTDeviceItem.TopContainer>
<BTDeviceItem.Content>
<BTDeviceItem.Title>{device.title}</BTDeviceItem.Title>
<Animated.View style={{ transform: [{ scale: pulseValue }] }}>
<BTDeviceItem.Status status={device.status} />
</Animated.View>
</BTDeviceItem.Content>
</BTDeviceItem.TopContainer>
</BTDeviceItem>
);
};
Testing
Component Testing
import React from "react";
import { render, fireEvent } from "@testing-library/react-native";
import { BTDeviceItem } from "@modules/bt-device";
import { DeviceStatus } from "@modules/bt-management";
describe("BTDeviceItem", () => {
const mockDevice = {
title: "Test Device",
subtitle: "Test Model",
status: DeviceStatus.CONNECTED,
deviceImageUri: "test-image-uri",
};
it("renders all sub-components correctly", () => {
const { getByText } = render(
<BTDeviceItem>
<BTDeviceItem.TopContainer>
<BTDeviceItem.Content>
<BTDeviceItem.Title>{mockDevice.title}</BTDeviceItem.Title>
<BTDeviceItem.SubTitle>{mockDevice.subtitle}</BTDeviceItem.SubTitle>
<BTDeviceItem.Status status={mockDevice.status} />
</BTDeviceItem.Content>
<BTDeviceItem.Image uri={mockDevice.deviceImageUri} />
</BTDeviceItem.TopContainer>
</BTDeviceItem>,
);
expect(getByText("Test Device")).toBeTruthy();
expect(getByText("Test Model")).toBeTruthy();
expect(getByText("Connected")).toBeTruthy();
});
it("handles press events correctly", () => {
const mockOnPress = jest.fn();
const { getByTestId } = render(
<BTDeviceItem testID="device-item" onPress={mockOnPress}>
<BTDeviceItem.TopContainer>
<BTDeviceItem.Title>Test Device</BTDeviceItem.Title>
</BTDeviceItem.TopContainer>
</BTDeviceItem>,
);
fireEvent.press(getByTestId("device-item"));
expect(mockOnPress).toHaveBeenCalled();
});
it("applies custom styling correctly", () => {
const customStyle = { backgroundColor: "red" };
const { getByTestId } = render(
<BTDeviceItem testID="device-item" style={customStyle}>
<BTDeviceItem.Title>Test</BTDeviceItem.Title>
</BTDeviceItem>,
);
const item = getByTestId("device-item");
expect(item.props.style).toContainEqual(
expect.objectContaining(customStyle),
);
});
it("displays correct status colors", () => {
const { getByText, rerender } = render(
<BTDeviceItem.Status status={DeviceStatus.CONNECTED} />,
);
const connectedStatus = getByText("Connected");
expect(connectedStatus.props.style).toContainEqual(
expect.objectContaining({
color: expect.any(String), // Success color
}),
);
rerender(<BTDeviceItem.Status status={DeviceStatus.DISCONNECTED} />);
const disconnectedStatus = getByText("Disconnected");
expect(disconnectedStatus.props.style).toContainEqual(
expect.objectContaining({
color: expect.any(String), // Error color
}),
);
});
});
Accessibility Testing
import { render } from "@testing-library/react-native";
import { BTDeviceItem } from "@modules/bt-device";
describe("BTDeviceItem Accessibility", () => {
it("has proper accessibility properties", () => {
const { getByRole } = render(
<BTDeviceItem
accessibilityLabel="Test device, connected"
accessibilityRole="button"
>
<BTDeviceItem.Title>Test Device</BTDeviceItem.Title>
</BTDeviceItem>,
);
const button = getByRole("button");
expect(button).toHaveAccessibilityLabel("Test device, connected");
});
it("groups child elements for screen readers", () => {
const { getByLabelText } = render(
<BTDeviceItem accessibilityLabel="Complete device info" accessible={true}>
<BTDeviceItem.Title accessible={false}>Device</BTDeviceItem.Title>
<BTDeviceItem.SubTitle accessible={false}>Model</BTDeviceItem.SubTitle>
</BTDeviceItem>,
);
expect(getByLabelText("Complete device info")).toBeTruthy();
});
});
Common Patterns
Grid Layout
const DeviceGrid = ({ devices }) => {
return (
<FlatList
data={devices}
numColumns={2}
renderItem={({ item }) => (
<View style={{ flex: 1, margin: 8 }}>
<BTDeviceItem onPress={() => handlePress(item)}>
<View style={{ alignItems: "center", padding: 16 }}>
<BTDeviceItem.Image uri={item.deviceImageUri} />
<BTDeviceItem.Title style={{ marginTop: 8, textAlign: "center" }}>
{item.title}
</BTDeviceItem.Title>
<BTDeviceItem.Status status={item.status} />
</View>
</BTDeviceItem>
</View>
)}
/>
);
};
Horizontal Carousel
const DeviceCarousel = ({ devices }) => {
return (
<FlatList
data={devices}
horizontal
showsHorizontalScrollIndicator={false}
ItemSeparatorComponent={() => <View style={{ width: 12 }} />}
renderItem={({ item }) => (
<BTDeviceItem style={{ width: 200 }} onPress={() => handlePress(item)}>
<BTDeviceItem.TopContainer>
<BTDeviceItem.Content>
<BTDeviceItem.Title numberOfLines={1}>
{item.title}
</BTDeviceItem.Title>
<BTDeviceItem.Status status={item.status} />
</BTDeviceItem.Content>
</BTDeviceItem.TopContainer>
</BTDeviceItem>
)}
/>
);
};
Status Dashboard
const StatusDashboard = ({ devices }) => {
const connectedCount = devices.filter(
(d) => d.status === DeviceStatus.CONNECTED,
).length;
return (
<View style={styles.dashboard}>
<Text style={styles.header}>
Connected: {connectedCount}/{devices.length}
</Text>
{devices.map((device, index) => (
<BTDeviceItem key={index} style={styles.dashboardItem}>
<BTDeviceItem.TopContainer>
<BTDeviceItem.Content style={{ flex: 1 }}>
<BTDeviceItem.Title>{device.title}</BTDeviceItem.Title>
</BTDeviceItem.Content>
<BTDeviceItem.Status status={device.status} />
</BTDeviceItem.TopContainer>
</BTDeviceItem>
))}
</View>
);
};
Best Practices
Component Design
- Composition: Use compound components for maximum flexibility
- Consistency: Maintain consistent layout patterns across device types
- Accessibility: Always include proper accessibility labels
- Performance: Memoize expensive calculations and event handlers
Styling
- Theme Usage: Always use theme values for colors, spacing, and typography
- Responsive Design: Test layouts on various screen sizes
- Visual Hierarchy: Use typography and color to establish clear hierarchy
- Touch Targets: Ensure touch targets meet minimum size requirements (44pt)
User Experience
- Loading States: Show loading indicators during state transitions
- Error States: Provide clear error messages and recovery options
- Feedback: Give immediate visual feedback for user interactions
- Context: Provide enough context for users to understand device capabilities
API Reference
BTDeviceItem
interface BTDeviceItemProps extends TouchableOpacityProps {
children: React.ReactNode;
}
BTDeviceItem.Status
interface BTDeviceItemStatusProps<T extends MD3TypescaleKey> {
status: DeviceStatus;
} & Omit<TextProps<T>, "children">;
Default Styling
The component includes built-in styling for elevation, shadows, borders, and colors that adapt to the app theme automatically.
Dependencies
react-native: Core React Native componentsreact-native-paper: Material Design components (Divider)@modules/theme: App theme system@modules/image: Optimized image components@modules/bt-management: Device status enums
Related Documentation
BTDeviceList: List component that uses BTDeviceItemDeviceListProvider: Context for device state managementTheme System: App theming and styling systemBT Management: Core Bluetooth device management