Skip to content

Storybook ​

The app uses Storybook for React Native with on-device rendering for component development and documentation.

Running Storybook ​

  1. Start the dev server: npm run start
  2. Launch the app: npm run ios or npm run android
  3. On the login screen, tap the book icon in the Dev Config Bar
  4. Storybook opens as a full-screen overlay

Dev Config Bar on login screen

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-generated

Writing 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-generate

This runs:

  1. scripts/add-assets-storybook.js - Generates Assets.stories.tsx from SVGs
  2. sb-rn-get-stories - Rebuilds story discovery cache

Configuration Files ​

FilePurpose
.rnstorybook/main.tsStory location and addons
.rnstorybook/preview.tsxGlobal preview config
.rnstorybook/index.tsStorybook entry point
.rnstorybook/storybook.requires.tsAuto-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 ​

  1. A story per variant - Ensure you can see a variant in isolation
  2. Export multiple variants - Have a story with all variants for comparison
  3. Use theme tokens - No hardcoded colors or spacing
  4. Add state for interactive demos - Show how components respond to input
  5. Use test factories for complex data - Consistent, realistic mock data
  6. Keep stories simple - Focus on the component, not business logic