Storybook ​
The app uses Storybook for React Native with on-device rendering for component development and documentation.
Running Storybook ​
- Start the dev server:
npm run start - Launch the app:
npm run iosornpm run android - On the login screen, tap the book icon in the Dev Config Bar
- Storybook opens as a full-screen overlay

TIP
Storybook is only available in development and staging builds, not production.
Story Location ​
Stories live in .rnstorybook/stories/:
.rnstorybook/stories/
├── Typography/
│ ├── Body.stories.tsx
│ ├── Heading.stories.tsx
│ └── Title.stories.tsx
├── TextInput/
│ ├── TextInput.stories.tsx
│ └── PasswordInput.stories.tsx
├── JobCards/
│ └── Service.stories.tsx
├── Button.stories.tsx
├── Modal.stories.tsx
└── Assets/Assets.stories.tsx # Auto-generatedWriting Stories ​
Basic Story ​
typescript
import React from 'react';
import {View} from 'react-native';
import {StyleSheet} from 'react-native-unistyles';
import Button from 'src/design-system/components/Button/Button';
export default {
title: 'Button',
component: Button,
};
export const Default = () => (
<View style={styles.container}>
<Button>Click me</Button>
</View>
);
export const Disabled = () => (
<View style={styles.container}>
<Button disabled>Disabled</Button>
</View>
);
const styles = StyleSheet.create((theme) => ({
container: {
flex: 1,
padding: theme.spacing.spacing4,
backgroundColor: theme.colors.colorBackgroundAlt,
},
}));Multiple Variants ​
typescript
export default {
title: 'Typography/Body',
};
export const Regular = () => <Body>Regular text</Body>;
export const Bold = () => <Body bold>Bold text</Body>;
export const Small = () => <Body small>Small text</Body>;Interactive Story with State ​
typescript
import {useState} from 'react';
export const WithValidation = () => {
const [error, setError] = useState('');
const handleChange = (text: string) => {
setError(text === '' ? 'Required field' : '');
};
return (
<TextInput
label="Name"
errorMessage={error}
onChangeText={handleChange}
/>
);
};Using Test Factories ​
For complex data like job cards:
typescript
import {factories} from 'test/factories';
import {formatDateISO} from 'src/utils/date/date';
import {startOfTomorrow} from 'date-fns';
export const JobCardStory = () => {
const job = factories.simplifiedService.build({
state: 'job_pending',
jobDate: formatDateISO(startOfTomorrow()),
});
return <JobCard job={job} onPress={() => {}} />;
};Story Title Conventions ​
typescript
// Simple component
export default {title: 'Button'};
// Categorized
export default {title: 'Typography/Body'};
// Deep nesting
export default {title: 'Job Cards/Installation/Solar'};Generating Stories ​
Regenerate story discovery and assets:
bash
npm run storybook-generateThis runs:
scripts/add-assets-storybook.js- GeneratesAssets.stories.tsxfrom SVGssb-rn-get-stories- Rebuilds story discovery cache
Configuration Files ​
| File | Purpose |
|---|---|
.rnstorybook/main.ts | Story location and addons |
.rnstorybook/preview.tsx | Global preview config |
.rnstorybook/index.ts | Storybook entry point |
.rnstorybook/storybook.requires.ts | Auto-generated, don't edit |
Styling in Stories ​
Always use Unistyles, matching the design system:
typescript
const styles = StyleSheet.create((theme) => ({
container: {
flex: 1,
padding: theme.spacing.spacing4,
backgroundColor: theme.colors.colorBackgroundAlt,
gap: theme.spacing.spacing2,
},
}));Best Practices ​
- A story per variant - Ensure you can see a variant in isolation
- Export multiple variants - Have a story with all variants for comparison
- Use theme tokens - No hardcoded colors or spacing
- Add state for interactive demos - Show how components respond to input
- Use test factories for complex data - Consistent, realistic mock data
- Keep stories simple - Focus on the component, not business logic