Content
A comprehensive content management system for React Native applications that provides components for displaying, organizing, and managing various types of content including articles, media, and rich text. The Content module offers three main compound components for different content presentation patterns.
Overview
The Content module is built around FHIR Composition resources and provides a complete ecosystem for content management in healthcare and general applications. It offers flexible, reusable components that handle everything from simple content cards to complex detailed content views with metadata, tags, and rich HTML rendering.
Key Features
- FHIR Integration: Native support for FHIR Composition resources with automatic data extraction
- Rich Text Rendering: Advanced HTML content rendering with custom image optimization
- Compound Components: Flexible component architecture for maximum customization
- Content Cards: Beautiful card layouts for content previews and listings
- Content Lists: Powerful list components with horizontal scrolling and sections
- Content Details: Full-featured content detail views with metadata and rich content
- Theme Integration: Seamless integration with app themes and Material Design 3
- Accessibility: Full accessibility support with screen readers and keyboard navigation
- Image Optimization: Built-in image optimization through OptimizedImage integration
- Internationalization: Built-in localization support for multilingual content
Components
ContentCard
A compound card component for displaying content previews with cover images, badges, and titles.
Sub-components:
ContentCard.Cover- Image cover with badge supportContentCard.CoverBadge- Overlay badge for coversContentCard.Title- Styled title textContentCard.Content- Card content container
ContentDetail
A comprehensive content detail view with FHIR Composition integration and rich content rendering.
Sub-components:
ContentDetail.Header- Content header sectionContentDetail.Title- Main content titleContentDetail.Metadata- Content metadata displayContentDetail.Body- Main content body containerContentDetail.RichContent- HTML content rendererContentDetail.Footer- Content footer sectionContentDetail.Tags- Content tags displayContentDetail.Resource- External resource links
ContentList
A flexible list component for organizing and displaying multiple content items with sections and horizontal scrolling.
Sub-components:
ContentList.Section- Content section containerContentList.Header- Section header with title and actionsContentList.Title- Section title textContentList.HorizontalList- Horizontal scrolling content listContentList.ViewAll- View all action button
Architecture
FHIR Integration
The Content module is designed around FHIR Composition resources, providing automatic extraction of:
- Title: From
composition.title - Content: From
composition.section[].text.div - Metadata: From
composition.meta(author, date, tags) - Resources: From
composition.section[]with resource links - Reading Time: From custom section with
reading-timecode
Content Rendering
- HTML Rendering: Uses
react-native-render-htmlfor rich content - Image Optimization: Automatic optimization through OptimizedImage
- Custom Renderers: Extensible rendering system for custom HTML elements
- Responsive Design: Automatic adaptation to screen sizes
Theme Integration
- Material Design 3: Full MD3 component integration
- Custom Themes: Seamless theme switching and customization
- Typography: Consistent typography using DMSans font family
- Spacing: Theme-based spacing system
- Colors: Automatic color adaptation based on theme
Usage Patterns
Basic Content Card
import { ContentCard } from "@ovok/native";
import { useRouter } from "expo-router";
import * as React from "react";
const content = {
id: "1",
title: "Understanding React Native",
image: "https://placehold.co/600x400/000000/FFFFFF/png",
};
const BasicContentCardExample = () => {
const router = useRouter();
return (
<ContentCard
onPress={() => router.navigate("/content-detail", { id: content.id })}
>
<ContentCard.Cover uri={content.image} badge="Featured" />
<ContentCard.Content>
<ContentCard.Title>{content.title}</ContentCard.Title>
</ContentCard.Content>
</ContentCard>
);
};
export default BasicContentCardExample;
Content Detail View
import { ContentDetail } from "@ovok/native";
import { Composition } from "@medplum/fhirtypes";
import * as React from "react";
const composition: Composition = {
resourceType: "Composition",
meta: {
profile: ["http://example.org/fhir/StructureDefinition/blog-post"],
tag: [
{
code: "test",
display: "test",
},
],
versionId: "f77b2a8b-5db2-4c64-bbc2-0907b2ab6eb3",
lastUpdated: "2025-03-25T11:37:54.897Z",
author: {
reference: "Practitioner/1dd3f5d5-043f-4c19-99c1-4ce087f919f3",
display: "Fatih Kayan",
},
project: "f950e8aa-63b1-4cc3-bc39-3fe05847c585",
compartment: [
{
reference: "Project/f950e8aa-63b1-4cc3-bc39-3fe05847c585",
},
],
},
identifier: {
system: "https://api.ovok.com/fhir/StructureDefinition/cms-blog",
value: "cms-blog",
},
status: "final",
date: "2025-03-25T11:37:54.730Z",
language: "en",
author: [
{
reference: "Practitioner/1dd3f5d5-043f-4c19-99c1-4ce087f919f3",
},
],
type: {
coding: [
{
system: "https://api.ovok.com/fhir/CodeSystem/cms-type",
code: "blog",
display: "blog",
},
],
},
category: [
{
coding: [
{
system: "https://api.ovok.com/fhir/CodeSystem/cms-category",
code: "New category",
display: "New category",
},
],
},
],
title: "Example app",
section: [
{
text: {
status: "generated",
div: '<div xmlns=\'http://www.w3.org/1999/xhtml\'><p><strong>Bold</strong></p>\n<p><strong><em>Italic</em></strong></p>\n<p><strong><em><ins>Underline</ins></em></strong></p>\n<p><del><strong><em><ins>Stroke</ins></em></strong></del></p>\n<h1><del><strong><em><ins>Heading</ins></em></strong></del></h1>\n<p><span style="font-size: 9px;">Small</span></p>\n<p><span style="font-size: 30px;">Big</span></p>\n<ul>\n<li><span style="font-size: 14px;">Bullet</span></li>\n</ul>\n<ol>\n<li><span style="font-size: 14px;">Number</span></li>\n</ol>\n<p style="text-align:right;"><span style="font-size: 14px;">Right</span></p>\n<p style="text-align:center;"><span style="font-size: 14px;">Center</span></p>\n<p style="text-align:justify;"><span style="color: rgb(26,188,156);font-size: 14px;">Colored</span></p>\n<p><span style="color: rgb(26,188,156);font-size: 14px;">😫</span></p>\n<p></p>\n<img src="https://api.ovok.com/binary/0b5b4257-0d41-448c-b200-a0d66ca1c0f5?publicResourceToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZWZlcmVuY2UiOiJEb2N1bWVudFJlZmVyZW5jZS8wYjViNDI1Ny0wZDQxLTQ0OGMtYjIwMC1hMGQ2NmNhMWMwZjUiLCJpYXQiOjE3NDI5MDI2MzV9.ISm4Wd10U4J0uTJmj6_c1xjfnJB_3xqNn0Mcll5wgzM&type=inline" alt="" style="height: auto;width: auto"/>\n<p></p>\n</div>',
},
extension: [
{
url: "https://api.ovok.com/binary/007c7a7a-d4df-4699-bd45-55c837ace49a?publicResourceToken=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyZWZlcmVuY2UiOiJEb2N1bWVudFJlZmVyZW5jZS8wMDdjN2E3YS1kNGRmLTQ2OTktYmQ0NS01NWM4MzdhY2U0OWEiLCJpYXQiOjE3NDI5MDI2NjF9.YSwvNehmHg8l3FmI2C_MvT9wC3n97o4FfegupTZrgtc&type=inline",
valueString: "cover-media",
},
],
code: {
coding: [
{
code: "main-content",
},
],
},
},
{
code: {
coding: [
{
code: "resource-link",
},
],
},
text: {
status: "generated",
div: "<div xmlns='http://www.w3.org/1999/xhtml'><a>https://google.com</a></div>",
},
},
{
code: {
coding: [
{
code: "reading-time",
},
],
},
text: {
status: "generated",
div: "<div xmlns='http://www.w3.org/1999/xhtml'>122 minutes</div>",
},
},
],
id: "d1235111-1f9e-4c37-b234-11e984a27ea0",
};
const ContentDetailExample = () => {
return (
<ContentDetail composition={composition}>
<ContentDetail.Header>
<ContentDetail.Title />
<ContentDetail.Metadata>
<ContentDetail.Metadata.Date />
<ContentDetail.Metadata.Separator />
<ContentDetail.Metadata.ReadingTime />
<ContentDetail.Metadata.Separator />
<ContentDetail.Metadata.Author />
</ContentDetail.Metadata>
</ContentDetail.Header>
<ContentDetail.Body>
<ContentDetail.RichContent />
</ContentDetail.Body>
<ContentDetail.Footer>
<ContentDetail.Tags />
<ContentDetail.Resource />
</ContentDetail.Footer>
</ContentDetail>
);
};
export default ContentDetailExample;
Content List with Sections
import { ContentCard, ContentList, useAppTheme } from "@ovok/native";
import { Composition } from "@medplum/fhirtypes";
import { Stack } from "expo-router";
import * as React from "react";
import { ListRenderItem, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
const contents = [
{
category: {
title: "Physical Health",
},
contents: [
{
resourceType: "Composition",
title: "Physical Health 101",
text: {
div: "Add description",
status: "generated",
},
section: [
{
code: {
id: "cover-image",
},
text: {
div: "<img src='https://dashboard.dev.ovok.com/api/storage/6714f6d5-c4d0-488d-84b3-cd0cced180e8/69119826-7b97-438d-823b-1164580ce535?Expires=1726779799218&Signature=PleNdPAl1Q1tdb6p4EMM070jYXgmOn2WJohWZW2p6tCw7E8bwxNM96pFO1JX8YzNuOjnBhqGC51unIwWDRkSQp%2BbJAommSHvQrhKUwOEA8krCCC81A1lAyTUjhPg6UP%2BeKpHnD8RBEqDRqT2x9ASK37pwZ8BkscQcolhWBpZMH0%3D' alt='cover-image' />",
status: "final",
},
entry: [
{
display:
"https://dashboard.dev.ovok.com/api/storage/6714f6d5-c4d0-488d-84b3-cd0cced180e8/69119826-7b97-438d-823b-1164580ce535?Expires=1726779799218&Signature=PleNdPAl1Q1tdb6p4EMM070jYXgmOn2WJohWZW2p6tCw7E8bwxNM96pFO1JX8YzNuOjnBhqGC51unIwWDRkSQp%2BbJAommSHvQrhKUwOEA8krCCC81A1lAyTUjhPg6UP%2BeKpHnD8RBEqDRqT2x9ASK37pwZ8BkscQcolhWBpZMH0%3D",
},
],
},
{
code: {
id: "text",
coding: [
{
code: "reading-time",
display: "1 min",
},
],
},
text: {
div: "<p>hey im batuhan how are you asgjagkalgjagajgagnoasgagasg</p>",
status: "final",
},
},
],
status: "active",
language: "en-GB",
category: [
{
coding: [
{
code: "language",
display: "en-GB",
},
],
text: "en-GB",
},
],
author: [
{
id: "d1ebaca2-efbd-4129-b849-338bf25f06b7",
resourceType: "Project",
display: "MindGuide",
},
],
subject: {
reference: "List/ff565b2b-3dc1-4d6a-ac13-29ebe8ab90cd",
},
type: {
id: "source",
text: "Source",
},
relatesTo: [
{
code: "appends",
targetReference: {
id: "urn:uuid:fd2c74a3-0adb-49d5-9904-fd053ad2e8be",
reference: "urn:uuid:fd2c74a3-0adb-49d5-9904-fd053ad2e8be",
},
},
],
date: "2024-09-19T20:03:19.114Z",
identifier: {
value: "content",
},
confidentiality: "N",
event: [
{
code: [
{
coding: [
{
code: "ae146a5d-6bac-45f9-b13d-c88ae08de576",
},
],
},
],
detail: [
{
reference: "CatalogEntry/ae146a5d-6bac-45f9-b13d-c88ae08de576",
},
],
},
],
id: "503c3ec2-e65f-4dbd-9104-7d28cf99a87a",
meta: {
versionId: "9eb3483d-53cf-4025-a795-306e60f2346d",
lastUpdated: "2024-09-19T20:03:19.465Z",
},
},
{
resourceType: "Composition",
meta: {
profile: ["http://example.org/fhir/StructureDefinition/blog-post"],
tag: [
{
code: "one",
display: "one",
},
{
code: "another",
display: "another",
},
],
versionId: "1e3cfad7-bf56-4afa-a10f-2a5393720607",
lastUpdated: "2024-12-03T09:11:02.178Z",
author: {
reference: "Practitioner/e4e6370a-828e-4316-acf8-2b42b7b4e276",
display: "Michael Immerza",
},
project: "9901f08b-9328-43fb-9745-9d19e8eec71a",
compartment: [
{
reference: "Project/9901f08b-9328-43fb-9745-9d19e8eec71a",
},
],
},
status: "final",
date: "2024-12-03T09:11:01.957Z",
language: "en",
author: [
{
reference: "Practitioner/e4e6370a-828e-4316-acf8-2b42b7b4e276",
},
],
type: {
coding: [
{
code: "template",
display: "template",
},
],
},
category: [
{
coding: [
{
code: "template",
},
],
},
],
title: "Very long title for testing a bit more text than expected",
section: [
{
text: {
status: "generated",
div: "<div xmlns='http://www.w3.org/1999/xhtml'><p><strong>Bold content<br><br></strong><strong><em>Bold italic</em></strong></p>\n</div>",
},
code: {
coding: [
{
code: "main-content",
},
],
},
entry: [
{
reference:
"DocumentReference/2c2787a3-2777-45b6-a599-cb5c893a7fb5",
},
],
},
],
id: "d45dc6ad-8fce-4cd1-8be0-b70ac712af0d",
},
{
resourceType: "Composition",
meta: {
profile: ["http://example.org/fhir/StructureDefinition/blog-post"],
tag: [
{
code: "one",
display: "one",
},
{
code: "another",
display: "another",
},
],
versionId: "1e3cfad7-bf56-4afa-a10f-2a5393720607",
lastUpdated: "2024-12-03T09:11:02.178Z",
author: {
reference: "Practitioner/e4e6370a-828e-4316-acf8-2b42b7b4e276",
display: "Michael Immerza",
},
project: "9901f08b-9328-43fb-9745-9d19e8eec71a",
compartment: [
{
reference: "Project/9901f08b-9328-43fb-9745-9d19e8eec71a",
},
],
},
status: "final",
date: "2024-12-03T09:11:01.957Z",
language: "en",
author: [
{
reference: "Practitioner/e4e6370a-828e-4316-acf8-2b42b7b4e276",
},
],
type: {
coding: [
{
code: "template",
display: "template",
},
],
},
category: [
{
coding: [
{
code: "template",
},
],
},
],
title: "Another title",
section: [
{
code: {
coding: [
{
code: "main-content",
},
],
},
text: {
status: "generated",
div: "<div xmlns='http://www.w3.org/1999/xhtml'><p><strong>Bold content<br><br></strong><strong><em>Bold italic</em></strong></p>\n</div>",
},
entry: [
{
reference:
"DocumentReference/2c2787a3-2777-45b6-a599-cb5c893a7fb5",
},
],
},
],
id: "d45dc6ad-8fce-4cd1-8be0-b70ac712af01",
},
] as Composition[],
},
{
category: {
title: "Mental Health",
},
contents: [
{
resourceType: "Composition",
title: "Mental Health 101",
text: {
div: "Add description",
status: "generated",
},
section: [
{
code: {
id: "cover-image",
},
text: {
div: "<img src='https://dashboard.dev.ovok.com/api/storage/6714f6d5-c4d0-488d-84b3-cd0cced180e8/69119826-7b97-438d-823b-1164580ce535?Expires=1726779799218&Signature=PleNdPAl1Q1tdb6p4EMM070jYXgmOn2WJohWZW2p6tCw7E8bwxNM96pFO1JX8YzNuOjnBhqGC51unIwWDRkSQp%2BbJAommSHvQrhKUwOEA8krCCC81A1lAyTUjhPg6UP%2BeKpHnD8RBEqDRqT2x9ASK37pwZ8BkscQcolhWBpZMH0%3D' alt='cover-image' />",
status: "final",
},
entry: [
{
display:
"https://dashboard.dev.ovok.com/api/storage/6714f6d5-c4d0-488d-84b3-cd0cced180e8/69119826-7b97-438d-823b-1164580ce535?Expires=1726779799218&Signature=PleNdPAl1Q1tdb6p4EMM070jYXgmOn2WJohWZW2p6tCw7E8bwxNM96pFO1JX8YzNuOjnBhqGC51unIwWDRkSQp%2BbJAommSHvQrhKUwOEA8krCCC81A1lAyTUjhPg6UP%2BeKpHnD8RBEqDRqT2x9ASK37pwZ8BkscQcolhWBpZMH0%3D",
},
],
},
{
code: {
id: "text",
coding: [
{
code: "reading-time",
display: "1 min",
},
],
},
text: {
div: "<p>hey im batuhan how are you asgjagkalgjagajgagnoasgagasg</p>",
status: "final",
},
},
],
status: "active",
language: "en-GB",
category: [
{
coding: [
{
code: "language",
display: "en-GB",
},
],
text: "en-GB",
},
],
author: [
{
id: "d1ebaca2-efbd-4129-b849-338bf25f06b7",
resourceType: "Project",
display: "MindGuide",
},
],
subject: {
reference: "List/ff565b2b-3dc1-4d6a-ac13-29ebe8ab90cd",
},
type: {
id: "source",
text: "Source",
},
relatesTo: [
{
code: "appends",
targetReference: {
id: "urn:uuid:fd2c74a3-0adb-49d5-9904-fd053ad2e8be",
reference: "urn:uuid:fd2c74a3-0adb-49d5-9904-fd053ad2e8be",
},
},
],
date: "2024-09-19T20:03:19.114Z",
identifier: {
value: "content",
},
confidentiality: "N",
event: [
{
code: [
{
coding: [
{
code: "ae146a5d-6bac-45f9-b13d-c88ae08de576",
},
],
},
],
detail: [
{
reference: "CatalogEntry/ae146a5d-6bac-45f9-b13d-c88ae08de576",
},
],
},
],
id: "503c3ec2-e65f-4dbd-9104-7d28cf99a87a",
meta: {
versionId: "9eb3483d-53cf-4025-a795-306e60f2346d",
lastUpdated: "2024-09-19T20:03:19.465Z",
},
},
{
resourceType: "Composition",
meta: {
profile: ["http://example.org/fhir/StructureDefinition/blog-post"],
tag: [
{
code: "one",
display: "one",
},
{
code: "another",
display: "another",
},
],
versionId: "1e3cfad7-bf56-4afa-a10f-2a5393720607",
lastUpdated: "2024-12-03T09:11:02.178Z",
author: {
reference: "Practitioner/e4e6370a-828e-4316-acf8-2b42b7b4e276",
display: "Michael Immerza",
},
project: "9901f08b-9328-43fb-9745-9d19e8eec71a",
compartment: [
{
reference: "Project/9901f08b-9328-43fb-9745-9d19e8eec71a",
},
],
},
status: "final",
date: "2024-12-03T09:11:01.957Z",
language: "en",
author: [
{
reference: "Practitioner/e4e6370a-828e-4316-acf8-2b42b7b4e276",
},
],
type: {
coding: [
{
code: "template",
display: "template",
},
],
},
category: [
{
coding: [
{
code: "template",
},
],
},
],
title: "Test title",
section: [
{
code: {
coding: [
{
code: "main-content",
},
],
},
text: {
status: "generated",
div: "<div xmlns='http://www.w3.org/1999/xhtml'><p><strong>Bold content<br><br></strong><strong><em>Bold italic</em></strong></p>\n</div>",
},
entry: [
{
reference:
"DocumentReference/2c2787a3-2777-45b6-a599-cb5c893a7fb5",
},
],
},
],
id: "d45dc6ad-8fce-4cd1-8be0-b70ac712af0d",
},
{
resourceType: "Composition",
meta: {
profile: ["http://example.org/fhir/StructureDefinition/blog-post"],
tag: [
{
code: "one",
display: "one",
},
{
code: "another",
display: "another",
},
],
versionId: "1e3cfad7-bf56-4afa-a10f-2a5393720607",
lastUpdated: "2024-12-03T09:11:02.178Z",
author: {
reference: "Practitioner/e4e6370a-828e-4316-acf8-2b42b7b4e276",
display: "Michael Immerza",
},
project: "9901f08b-9328-43fb-9745-9d19e8eec71a",
compartment: [
{
reference: "Project/9901f08b-9328-43fb-9745-9d19e8eec71a",
},
],
},
status: "final",
date: "2024-12-03T09:11:01.957Z",
language: "en",
author: [
{
reference: "Practitioner/e4e6370a-828e-4316-acf8-2b42b7b4e276",
},
],
type: {
coding: [
{
code: "template",
display: "template",
},
],
},
category: [
{
coding: [
{
code: "template",
},
],
},
],
title: "Last title",
section: [
{
code: {
coding: [
{
code: "main-content",
},
],
},
text: {
status: "generated",
div: "<div xmlns='http://www.w3.org/1999/xhtml'><p><strong>Bold content<br><br></strong><strong><em>Bold italic</em></strong></p>\n</div>",
},
entry: [
{
reference:
"DocumentReference/2c2787a3-2777-45b6-a599-cb5c893a7fb5",
},
],
},
],
id: "d45dc6ad-8fce-4cd1-8be0-b70ac712af03",
},
] as Composition[],
},
];
type Content = {
id: string;
title: string;
imageUrl: string;
};
type CategoryContent = {
category: {
id: string;
title: string;
};
contents: Content[];
};
const featuredContent = contents[0]?.contents?.[0];
const ContentListScreen = () => {
const { bottom: bottomInset } = useSafeAreaInsets();
const theme = useAppTheme();
const mappedContents = React.useMemo(() => {
return contents.map((content, categoryIndex) => ({
category: {
id: `category-${categoryIndex}`,
title: content.category.title,
},
contents: content.contents.map((content, contentIndex) => ({
id:
content.id ??
`content-category-${categoryIndex}-content-${contentIndex}`,
title: content.title,
imageUrl: `https://picsum.photos/id/${
categoryIndex + 1
}${contentIndex}/200`,
})),
}));
}, []);
const renderHeader = React.useCallback(() => {
return (
<View
style={{
paddingHorizontal: theme.spacing(2),
}}
>
<ContentCard>
<ContentCard.Cover
uri="https://picsum.photos/200"
badge="Today's Selection"
/>
<ContentCard.Content>
<ContentCard.Title>{featuredContent?.title}</ContentCard.Title>
</ContentCard.Content>
</ContentCard>
</View>
);
}, [theme]);
const renderItem: ListRenderItem<CategoryContent> = React.useCallback(
({ item }) => (
<ContentList.Section style={{ paddingHorizontal: theme.spacing(2) }}>
<View
style={{
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
}}
>
<ContentList.Title>{item.category.title}</ContentList.Title>
<ContentList.ViewAll onPress={() => {}} />
</View>
<ContentList.HorizontalList
horizontalPadding={theme.spacing(2)}
data={item.contents}
renderItem={({ item: content }) => (
<ContentCard style={{ width: 200 }}>
<ContentCard.Cover
uri={content.imageUrl}
style={{
height: 134,
borderRadius: 12,
}}
/>
<ContentCard.Content>
<ContentCard.Title variant="titleMedium">
{content.title}
</ContentCard.Title>
</ContentCard.Content>
</ContentCard>
)}
/>
</ContentList.Section>
),
[theme],
);
return (
<>
<Stack.Screen
options={{
headerTitle: "Content List",
}}
/>
<ContentList
data={mappedContents}
ListHeaderComponent={renderHeader}
renderItem={renderItem}
contentContainerStyle={{
paddingVertical: theme.spacing(2),
paddingBottom: bottomInset + theme.spacing(2),
gap: theme.spacing(5),
}}
/>
</>
);
};
export default ContentListScreen;
Healthcare Content Use Cases
Patient Education
import { ContentDetail } from "@ovok/native";
import { Composition } from "@medplum/fhirtypes";
import * as React from "react";
const educationComposition: Composition = {
resourceType: "Composition",
title: "Managing Diabetes - Patient Education",
status: "final",
date: "2024-12-20T10:00:00Z",
meta: {
tag: [
{ code: "patient-education", display: "Patient Education" },
{ code: "diabetes", display: "Diabetes" },
],
},
section: [
{
text: {
status: "generated",
div: '<div xmlns="http://www.w3.org/1999/xhtml"><h2>Understanding Diabetes</h2><p>Diabetes is a condition where your blood glucose levels are too high...</p></div>',
},
code: { coding: [{ code: "main-content" }] },
},
{
code: { coding: [{ code: "reading-time" }] },
text: { status: "generated", div: "5 min read" },
},
],
id: "patient-education-diabetes",
};
const PatientEducationExample = () => {
const handleTagPress = (tag: any) => {
console.log("Tag pressed:", tag);
};
return (
<ContentDetail composition={educationComposition}>
<ContentDetail.Header>
<ContentDetail.Title />
<ContentDetail.Metadata>
<ContentDetail.Metadata.Date />
<ContentDetail.Metadata.Separator />
<ContentDetail.Metadata.ReadingTime />
</ContentDetail.Metadata>
</ContentDetail.Header>
<ContentDetail.Body>
<ContentDetail.RichContent />
</ContentDetail.Body>
<ContentDetail.Footer>
<ContentDetail.Tags onTagPress={handleTagPress} />
<ContentDetail.Resource />
</ContentDetail.Footer>
</ContentDetail>
);
};
export default PatientEducationExample;
Clinical Guidelines
import { ContentCard, ContentList } from "@ovok/native";
import * as React from "react";
type Guideline = {
id: string;
title: string;
imageUrl: string;
urgency: "High" | "Medium" | "Low";
};
const clinicalGuidelines = [
{
category: { title: "Cardiology" },
contents: [
{
id: "1",
title: "Hypertension Management Guidelines",
imageUrl: "https://picsum.photos/200/150?random=1",
urgency: "High" as const,
},
{
id: "2",
title: "Heart Failure Treatment Protocol",
imageUrl: "https://picsum.photos/200/150?random=2",
urgency: "Medium" as const,
},
],
},
{
category: { title: "Endocrinology" },
contents: [
{
id: "3",
title: "Diabetes Management Protocol",
imageUrl: "https://picsum.photos/200/150?random=3",
urgency: "High" as const,
},
],
},
];
const ClinicalGuidelinesExample = () => {
const handleViewGuidelines = (category: string) => {
console.log("View all guidelines for:", category);
};
const handleOpenGuideline = (guideline: Guideline) => {
console.log("Open guideline:", guideline.title);
};
return (
<ContentList
data={clinicalGuidelines}
renderItem={({ item }) => (
<ContentList.Section>
<ContentList.Header>
<ContentList.Title>{item.category.title}</ContentList.Title>
<ContentList.ViewAll
onPress={() => handleViewGuidelines(item.category.title)}
/>
</ContentList.Header>
<ContentList.HorizontalList
data={item.contents}
renderItem={({ item: guideline }) => (
<ContentCard onPress={() => handleOpenGuideline(guideline)}>
<ContentCard.Cover
uri={guideline.imageUrl}
badge={guideline.urgency}
/>
<ContentCard.Content>
<ContentCard.Title>{guideline.title}</ContentCard.Title>
</ContentCard.Content>
</ContentCard>
)}
/>
</ContentList.Section>
)}
/>
);
};
export default ClinicalGuidelinesExample;
Medical Articles
import { ContentCard } from "@ovok/native";
import * as React from "react";
type MedicalArticle = {
id: string;
title: string;
imageUrl: string;
isLatest: boolean;
};
const article: MedicalArticle = {
id: "med-article-1",
title: "Latest Advances in Cardiovascular Treatment",
imageUrl: "https://picsum.photos/300/200?random=medical",
isLatest: true,
};
const MedicalArticleExample = () => {
const handleOpenArticle = (article: MedicalArticle) => {
console.log("Opening article:", article.title);
};
return (
<ContentCard onPress={() => handleOpenArticle(article)}>
<ContentCard.Cover
uri={article.imageUrl}
badge={article.isLatest ? "Latest" : undefined}
/>
<ContentCard.Content>
<ContentCard.Title numberOfLines={2}>{article.title}</ContentCard.Title>
</ContentCard.Content>
</ContentCard>
);
};
export default MedicalArticleExample;
Performance Considerations
- Image Optimization: Automatic image optimization and caching
- List Performance: Optimized FlatList implementation for large datasets
- HTML Rendering: Efficient HTML parsing and rendering
- Memory Management: Automatic cleanup of resources and subscriptions
- Lazy Loading: Support for lazy loading of images and content
- Caching: Built-in caching for improved performance
Accessibility
- Screen Readers: Full VoiceOver and TalkBack support
- Focus Management: Proper focus order and navigation
- Touch Targets: Minimum 44pt touch target sizes
- Color Contrast: High contrast mode support
- Dynamic Type: Automatic font scaling support
- Keyboard Navigation: Full keyboard accessibility
Internationalization
- Localization Support: Built-in i18n integration
- RTL Support: Right-to-left language support
- Date Formatting: Locale-aware date formatting
- Number Formatting: Locale-aware number formatting
Dependencies
react-native-paper: Material Design componentsreact-native-render-html: HTML content rendering@medplum/fhirtypes: FHIR TypeScript definitionsdate-fns: Date formatting utilitiesreact-i18next: Internationalization frameworkexpo-web-browser: External link handling