Previously on the journey:
- The application's backend is solid: Service → Repository → Encrypted Realm.
- The UI vision is clear: On Day 8, we used an AI design tool ("Stitch") to generate beautiful, minimal mockups and, crucially, a set of corresponding HTML and CSS files.
The weekend is over. It’s Monday. The architectural foundation is set, and the design blueprint is in hand. Today, we build the bridge between them. We will bring the main Archive Timeline screen to life.
But we won’t do it alone. In the spirit of this "AI era" journey, we’ll use another AI tool, GitHub Copilot, as our intelligent pairing partner to translate the web design into native reality.
The Challenge: A Tale of Two Worlds
The AI design tool gave us web code. It thinks in terms of the Document Object Model (DOM): <div>s, <p>s, and CSS properties like box-shadow.
React Native, however, builds for a different world. It commands native UI components: <View>, <Text>, and uses a JavaScript-based styling system (StyleSheet) that doesn't understand CSS syntax directly.
| Web (From AI Tool) | React Native (Our Goal) |
|---|---|
<div> |
<View> |
<p>, <h2> |
<Text> |
class="card" |
style={styles.card} |
box-shadow: ... |
shadowColor, shadowOffset, elevation... |
We can't just copy-paste. We must translate. And for this translation work, a tool like GitHub Copilot is not a shortcut—it’s a force multiplier.
The Process: Prompt-Driven Translation with GitHub Copilot
Our goal is to create src/screens/ArchiveTimelineScreen.tsx. We'll do this by giving Copilot clear instructions, context, and code to work with.
Step 1: Create the Component and Provide Context
First, create the file. To give Copilot the best possible context, we'll paste the HTML and CSS from our AI design tool directly into the file as comments.
File: src/screens/ArchiveTimelineScreen.tsx
// The AI design tool gave us this HTML structure for an entry card:
/*
<div class="entry-card">
<p class="entry-date">2026-03-01</p>
<h2 class="entry-title">First day of spring</h2>
<p class="entry-preview">Walked through the park and felt the sun.</p>
<div class="tags-container">
<span class="tag">nature</span>
<span class="tag">walk</span>
</div>
</div>
*/
// And it gave us this CSS:
/*
.entry-card {
background-color: #ffffff;
border-radius: 12px;
padding: 20px;
margin-bottom: 15px;
border: 1px solid #dee2e6;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
}
.entry-date {
font-size: 12px;
color: #868e96;
margin-bottom: 5px;
}
... (and so on for other classes)
*/
// Now, let's start building.
Step 2: Prompting for JSX Structure
With the context in place, we can write a prompt (as a comment) asking Copilot to generate the JSX.
// PROMPT for Copilot:
// Based on the HTML comment above, create a stateless React Native component named EntryCard
// that takes `item` as a prop. Use <View> and <Text> components.
// --- After this line, Copilot will suggest the following code ---
Copilot will generate a functional component that looks remarkably close to what we need, correctly mapping divs and paragraphs to Views and Texts.
Step 3: Prompting for Style Translation
This is the magic step. We ask Copilot to perform the translation from CSS to a StyleSheet object.
// PROMPT for Copilot:
// Now, convert the CSS comment block above into a React Native StyleSheet object named 'styles'.
// Convert properties to camelCase.
// For the 'box-shadow', create a cross-platform solution using 'shadow' properties for iOS
// and 'elevation' for Android.
// --- Copilot's turn ---
By providing a specific instruction for box-shadow, we guide the AI to handle the most complex part of the translation correctly. This is the difference between a generic prompt and an expert one.
Step 4: Assemble, Review, and Refine
Copilot isn't perfect; it's a "co-pilot." It provides a high-quality draft. Our job as the lead pilot is to review, refine, and integrate. We will now integrate three things: the JSX structure from Step 2, the StyleSheet from Step 3, and a custom hook to provide data while respecting our application's architecture.
Architectural Integrity: The useArchiveEntries Hook
Our UI component must not fetch data directly. That's the job of the service layer, as we defined on Day 7. To keep this boundary clean, we'll create a custom hook, useArchiveEntries, that will act as the middleman. For today's UI work, it will simply return mock data, but in a future chapter, we will upgrade it to call our ArchiveService.
Create the following file:
File: src/hooks/useArchiveEntries.ts
import { useState, useEffect } from 'react';
// Type for an archive entry
export interface ArchiveEntry {
id: string;
title: string;
preview: string;
date: string;
tags: string[];
}
// Mock data that matches our design for now
const MOCK_ENTRIES: ArchiveEntry[] = [
{ id: '1', title: 'First day of spring', preview: 'Walked through the park and felt the sun.', date: '2026-03-01', tags: ['nature', 'walk'] },
{ id: '2', title: 'A new project idea', preview: 'Sketched out a plan for a new application.', date: '2026-02-28', tags: ['work', 'creative'] },
{ id: '3', title: 'Reading by the window', preview: 'Finished the last chapter of a great book.', date: '2026-02-25', tags: ['books', 'calm'] },
];
export const useArchiveEntries = (): { entries: ArchiveEntry[] } => {
const [entries, setEntries] = useState<ArchiveEntry[]>([]);
useEffect(() => {
// In a future chapter, this will call our ArchiveService.
// For now, we simulate an API call with mock data.
setEntries(MOCK_ENTRIES);
}, []);
return {
entries,
// We can add loading/error states here later
};
};
With this hook created, we can now assemble our final component, confident that our architecture remains clean.
One such refinement is handling the "safe area" (the part of the screen not obscured by notches or home indicators). While React Native includes a basic SafeAreaView, the react-native-safe-area-context package provides a more powerful and reliable implementation that works seamlessly with navigation. We'll use that one.
If you haven't already, install it:
npm install react-native-safe-area-context
The Final Code (AI-Assisted)
After assembling the pieces and performing minor touch-ups, our final component looks like this:
File: src/screens/ArchiveTimelineScreen.tsx
import React from 'react';
import { View, Text, StyleSheet, FlatList, TouchableOpacity } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { useArchiveEntries } from '../hooks/useArchiveEntries'; // Respecting our architecture
/**
* A card component to display a single archive entry.
* Its structure was first drafted by GitHub Copilot based on an HTML comment.
*/
const EntryCard = ({ item }) => (
<View style={styles.entryCard}>
<Text style={styles.entryDate}>{item.date}</Text>
<Text style={styles.entryTitle}>{item.title}</Text>
<Text style={styles.entryPreview}>{item.preview}</Text>
<View style={styles.tagsContainer}>
{item.tags.map(tag => <Text key={tag} style={styles.tag}>{tag}</Text>)}
</View>
</View>
);
const ArchiveTimelineScreen = () => {
const { entries } = useArchiveEntries();
return (
<SafeAreaView style={styles.container}>
<Text style={styles.headerTitle}>Archive</Text>
<View style={styles.searchBar}>
<Text style={styles.searchText}>Search...</Text>
</View>
<FlatList
data={entries}
renderItem={({ item }) => <EntryCard item={item} />}
keyExtractor={(item) => item.id}
contentContainerStyle={styles.listContent}
showsVerticalScrollIndicator={false}
/>
<TouchableOpacity style={styles.fab}>
<Text style={styles.fabText}>+</Text>
</TouchableOpacity>
</SafeAreaView>
);
};
/**
* These styles were translated from CSS by GitHub Copilot, with manual review
* and refinement, especially for the cross-platform shadow properties.
*/
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f8f9fa', // Off-white
},
headerTitle: {
fontSize: 32,
fontWeight: 'bold',
marginTop: 20,
marginBottom: 20,
paddingHorizontal: 20,
},
searchBar: {
backgroundColor: '#e9ecef',
borderRadius: 10,
padding: 15,
marginHorizontal: 20,
marginBottom: 20,
},
searchText: {
color: '#868e96',
},
listContent: {
paddingHorizontal: 20,
paddingBottom: 100, // Ensure space for the FAB
},
entryCard: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 20,
marginBottom: 15,
// Shadow properties refined from Copilot's suggestion
shadowColor: '#000', // iOS
shadowOffset: { width: 0, height: 2 }, // iOS
shadowOpacity: 0.08, // iOS
shadowRadius: 4, // iOS
elevation: 3, // Android
},
entryDate: {
fontSize: 12,
color: '#868e96',
marginBottom: 5,
},
entryTitle: {
fontSize: 18,
fontWeight: '600',
marginBottom: 5,
color: '#212529',
},
entryPreview: {
fontSize: 14,
color: '#495057',
lineHeight: 20,
},
tagsContainer: {
flexDirection: 'row',
marginTop: 12,
flexWrap: 'wrap',
},
tag: {
backgroundColor: '#e9ecef',
color: '#495057',
fontSize: 12,
paddingHorizontal: 10,
paddingVertical: 5,
borderRadius: 16,
marginRight: 8,
marginBottom: 8,
overflow: 'hidden',
},
fab: {
position: 'absolute',
right: 25,
bottom: 40,
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: '#ced4da',
justifyContent: 'center',
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.2,
shadowRadius: 5,
elevation: 8,
},
fabText: {
fontSize: 30,
color: '#495057',
fontWeight: '300',
},
});
export default ArchiveTimelineScreen;
A Quick Detour: Absolute Import Aliases
As our project grows, you'll notice import paths getting longer and more fragile.
import { useArchiveEntries } from '../hooks/useArchiveEntries';
This is called a relative path. If we move this screen to a different folder, the ../ part breaks. It’s brittle.
The solution is to use absolute import aliases. We can tell our project that the special character @/ always means "start from the src/ directory."
This is a two-step process:
1. Configure tsconfig.json
Add the baseUrl and paths properties to your tsconfig.json file. This tells TypeScript how to resolve the @/ alias.
File: tsconfig.json
{
"compilerOptions": {
// ... existing options
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
}
// ... rest of the file
}
2. Update Babel (for Metro Bundler)
React Native's bundler, Metro, doesn't read tsconfig.json. We need to tell it about our alias separately using the babel-plugin-module-resolver.
First, install it:
npm install --save-dev babel-plugin-module-resolver
Next, create or update your babel.config.js file in the project root:
File: babel.config.js
module.exports = {
presets: ['module:metro-react-native-babel-preset'],
plugins: [
[
'module-resolver',
{
root: ['./src'],
extensions: ['.ios.js', '.android.js', '.js', '.ts', '.tsx', '.json'],
alias: {
"@": "./src",
}
}
]
]
};
The Result: Cleaner, Stronger Imports
Now, we can refactor our imports to be much cleaner and more robust.
Before:
import { useArchiveEntries } from '../hooks/useArchiveEntries';
After:
import { useArchiveEntries } from '@/hooks/useArchiveEntries';
No matter where we move our screen file, this import will never break. It's a small change that pays huge dividends in maintainability.
Prompt to refactor the screen using 5 Layer architecture
refactor ArchiveTimelineScreen.tsx following 5_layer_architecture.md
If you are providing prompt at design time:
create ArchiveTimelineScreen.tsx. match the design with code.html and follow architecture from 5_layer_architecture.md
🎯 Day 11 Outcome
Today, we successfully transformed a static web design into a live, styled React Native component. We didn't just write code; we directed an AI to do the heavy lifting of translation for us.
The key takeaway is a new development skill: Prompt Engineering. Our value was not in typing backgroundColor a dozen times, but in crafting a precise prompt that instructed an AI to do it correctly, and then using our expertise to validate and refine the result. We acted as architects and quality assurance, not just as bricklayers.
Next up: The screen looks beautiful but is static. On Day 12, we will bring it to life by implementing navigation, making the floating action button open the Create New Entry screen.
🔧 Your Turn: Practical Exercises
- Translate Another Component: Take the HTML/CSS for the "New Entry" screen from the Day 8 files and use the same prompt-driven process to create a new
CreateEntryScreen.tsxcomponent. - Implement the Empty State: Our current code assumes there are always entries. Modify
ArchiveTimelineScreen.tsxto show the "empty state" design from Day 8 when theentriesarray is empty. (Hint: Use a conditional render:entries.length === 0 ? <EmptyStateComponent /> : <FlatList ... />). - Experiment with Prompts: Go back to the style translation prompt. Change it to be less specific (e.g., remove the instruction about
box-shadow). How does Copilot's output change? Now, try being more specific (e.g., "Use a larger shadow radius for a softer feel"). Observe the difference.
📚 Further Reading
- React Hooks: Official Introduction to Hooks - The best place to start understanding
useState,useEffect, and the logic behind custom hooks. - Building Your Own Hooks: React Docs on Custom Hooks - A guide to extracting component logic into reusable functions, just like we did with
useArchiveEntries. - React Native Styling: StyleSheet API Documentation - The official reference for React Native's styling system.
- Styling Cheatsheet: React Native vs. Web CSS - An excellent resource for manual style translations.
- GitHub Copilot Best Practices: Getting the most out of GitHub Copilot - Tips for writing effective prompts.