Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/satsigner/satsigner/llms.txt

Use this file to discover all available pages before exploring further.

Overview

SatSigner’s component library is built with React Native and TypeScript, providing a consistent, reusable UI system across the mobile application. All components follow strict typing conventions and are documented using Storybook for interactive development and testing.

Component Architecture

Base Components

Components in SatSigner follow a consistent pattern with TypeScript prop types and style composition:
components/SSButton.tsx
import { StyleSheet, TouchableOpacity } from 'react-native'
import { useMemo } from 'react'

export type SSButtonProps = {
  label: string
  variant?:
    | 'default'
    | 'secondary'
    | 'outline'
    | 'ghost'
    | 'subtle'
    | 'gradient'
    | 'danger'
  loading?: boolean
  withSelect?: boolean
  uppercase?: boolean
  gradientType?: 'default' | 'special'
  textStyle?: StyleProp<TextStyle>
} & React.ComponentPropsWithoutRef<typeof TouchableOpacity>

function SSButton({
  label,
  variant = 'default',
  loading,
  disabled,
  style,
  ...props
}: SSButtonProps) {
  const buttonStyle = useMemo(() => {
    let buttonVariantStyles = styles.buttonDefault
    if (variant === 'secondary') buttonVariantStyles = styles.buttonSecondary
    if (variant === 'outline') buttonVariantStyles = styles.buttonOutline
    // ... variant logic
    
    return StyleSheet.compose(
      {
        ...styles.buttonBase,
        ...(disabled ? styles.disabled : {}),
        ...buttonVariantStyles
      },
      style
    )
  }, [variant, disabled, style])

  return (
    <TouchableOpacity
      style={buttonStyle}
      activeOpacity={0.6}
      disabled={disabled || loading}
      {...props}
    >
      {/* Component content */}
    </TouchableOpacity>
  )
}

Type-Safe Props

All components use TypeScript interfaces extending base React Native component props:
components/SSText.tsx
export type SSTextProps = {
  color?: 'white' | 'black' | 'muted'
  size?: TextFontSize
  type?: 'sans-serif' | 'mono'
  weight?: TextFontWeight
  uppercase?: boolean
  center?: boolean
} & React.ComponentPropsWithoutRef<typeof Text>

Style Composition

Use useMemo for performance and StyleSheet.compose for style merging:
const textStyle = useMemo(() => {
  const colorStyle = {
    white: styles.textColorWhite,
    black: styles.textColorBlack,
    muted: styles.textColorMuted
  }[color]

  return StyleSheet.compose(
    {
      ...styles.textBase,
      ...colorStyle,
      fontSize: Sizes.text.fontSize[size],
      ...(uppercase ? styles.uppercase : {})
    },
    style
  )
}, [color, size, uppercase, style])

Component Categories

Form Components

  • SSButton - Primary button with variants (default, secondary, outline, ghost, gradient, danger)
  • SSTextInput - Text input with label and validation support
  • SSCheckbox - Checkbox with label and state management
  • SSRadioButton - Radio button for single selection
  • SSSwitch - Toggle switch component
  • SSSlider - Value slider with min/max bounds
  • SSAmountInput - Bitcoin amount input with sat/BTC conversion
  • SSFeeInput - Transaction fee input with rate calculation

Bitcoin-Specific Components

components/SSUtxoCard.tsx
import { useLocalSearchParams, useRouter } from 'expo-router'
import { TouchableOpacity } from 'react-native'
import { useShallow } from 'zustand/react/shallow'

type SSUtxoCardProps = {
  utxo: Utxo
  totalBalance?: number
}

function SSUtxoCard({ utxo, totalBalance }: SSUtxoCardProps) {
  const [fiatCurrency, satsToFiat] = usePriceStore(
    useShallow((state) => [state.fiatCurrency, state.satsToFiat])
  )
  const [currencyUnit, useZeroPadding] = useSettingsStore(
    useShallow((state) => [state.currencyUnit, state.useZeroPadding])
  )

  const router = useRouter()
  const { id } = useLocalSearchParams<AccountSearchParams>()
  const { txid, vout } = utxo

  return (
    <TouchableOpacity
      onPress={() =>
        router.navigate(
          `/signer/bitcoin/account/${id}/transaction/${txid}/utxo/${vout}`
        )
      }
    >
      {totalBalance !== undefined && totalBalance > 0 && (
        <SSUtxoBar utxoValue={utxo.value} totalBalance={totalBalance} />
      )}
      {/* UTXO details display */}
    </TouchableOpacity>
  )
}

Data Display Components

  • SSTransactionCard - Transaction list item with type, amount, timestamp
  • SSUtxoCard - UTXO display with address and value
  • SSAddressCard - Address display with QR code and copy
  • SSAccountCard - Account overview with balance
  • SSDetailsList - Key-value pairs for detail screens

Visualization Components

  • SSBubbleChart - UTXO visualization as bubbles
  • SSHistoryChart - Balance history over time
  • SSTransactionChart - Transaction flow visualization
  • SSFeeRateChart - Fee rate distribution
  • SSSankeyDiagram - Transaction flow Sankey diagram

Storybook Integration

Configuration

Storybook is configured in .storybook/main.ts:
.storybook/main.ts
import type { StorybookConfig } from '@storybook/react-native'

const main: StorybookConfig = {
  stories: ['../components/**/*.stories.?(ts|tsx|js|jsx)'],
  addons: [
    '@storybook/addon-ondevice-controls',
    '@storybook/addon-ondevice-actions',
    '@storybook/addon-ondevice-backgrounds',
    '@storybook/addon-ondevice-notes'
  ]
}

export default main

Writing Stories

Create stories using the Component Story Format (CSF):
components/SSButton.stories.tsx
import { withBackgrounds } from '@storybook/addon-ondevice-backgrounds'
import type { Meta, StoryObj } from '@storybook/react'
import { storybookBackgrounds } from '@/.storybook/utils/backgrounds'
import SSButton from './SSButton'
import storybookLayoutDecorator from './SSStoryBookLayout'

const meta = {
  title: 'SSButton',
  component: SSButton,
  args: {
    label: 'Satsigner',
    variant: 'default',
    gradientType: 'default'
  },
  argTypes: {
    variant: {
      control: 'select',
      options: [
        'default',
        'secondary',
        'outline',
        'ghost',
        'subtle',
        'gradient',
        'danger'
      ]
    },
    loading: {
      control: 'boolean',
      description: 'Button loading'
    },
    withSelect: {
      control: 'boolean',
      description: 'With select icon'
    },
    uppercase: {
      control: 'boolean',
      description: 'Text uppercase'
    }
  },
  decorators: [storybookLayoutDecorator, withBackgrounds],
  parameters: {
    backgrounds: storybookBackgrounds
  }
} satisfies Meta<typeof SSButton>

export default meta

type Story = StoryObj<typeof meta>

export const Default: Story = {}

export const Secondary: Story = {
  args: {
    variant: 'secondary'
  }
}

export const Gradient: Story = {
  args: {
    variant: 'gradient'
  },
  argTypes: {
    gradientType: {
      control: 'select',
      options: ['default', 'special']
    }
  }
}

Running Storybook

Run Storybook in development mode:
# iOS with Storybook enabled
yarn sb:ios

# Android with Storybook enabled
yarn sb:android
Storybook is enabled via the EXPO_PUBLIC_STORYBOOK_ENABLED environment variable.

State Management Integration

Components integrate with Zustand stores using the useShallow selector for performance:
import { useShallow } from 'zustand/react/shallow'
import { usePriceStore } from '@/store/price'
import { useSettingsStore } from '@/store/settings'

function MyComponent() {
  // Select only needed values to prevent unnecessary re-renders
  const [fiatCurrency, satsToFiat] = usePriceStore(
    useShallow((state) => [state.fiatCurrency, state.satsToFiat])
  )
  
  const [currencyUnit, useZeroPadding] = useSettingsStore(
    useShallow((state) => [state.currencyUnit, state.useZeroPadding])
  )
  
  // Component logic
}

Styling Best Practices

Theme System

Use centralized style constants from @/styles:
import { Colors, Sizes, Typography } from '@/styles'

const styles = StyleSheet.create({
  buttonBase: {
    borderRadius: Sizes.button.borderRadius,
    height: Sizes.button.height,
    width: '100%'
  },
  buttonDefault: {
    backgroundColor: Colors.gray[600]
  },
  textDefault: {
    color: Colors.white,
    fontFamily: Typography.sfProTextRegular,
    fontSize: Sizes.text.fontSize.sm
  }
})

Responsive Design

Handle different screen sizes using layout components:
import SSHStack from '@/layouts/SSHStack'
import SSVStack from '@/layouts/SSVStack'

<SSVStack gap="md">
  <SSHStack justifyBetween alignCenter>
    <SSText>Label</SSText>
    <SSText>Value</SSText>
  </SSHStack>
</SSVStack>

Component Naming Convention

All SatSigner components are prefixed with SS:
  • SS prefix identifies SatSigner custom components
  • PascalCase for component names: SSButton, SSTransactionCard
  • File names match component names: SSButton.tsx
  • Story files end with .stories.tsx: SSButton.stories.tsx

Testing Components

Components should be tested through:
  1. Storybook stories for visual testing and documentation
  2. Unit tests for logic and state management (see Testing)
  3. Integration tests for user interactions and navigation flows

Best Practices

  1. Type Everything - Use strict TypeScript types for all props
  2. Memoization - Use useMemo and useCallback for expensive computations
  3. Shallow Selectors - Use useShallow with Zustand to prevent unnecessary re-renders
  4. Style Composition - Use StyleSheet.compose to merge styles efficiently
  5. Accessibility - Add proper accessibility props (accessibilityLabel, accessibilityRole)
  6. Error Boundaries - Wrap components that fetch data in error boundaries
  7. Loading States - Show loading indicators for async operations
  8. Responsive - Test components on different screen sizes

Component Documentation

Every component should include:
  • JSDoc comments describing the component’s purpose
  • Prop types with descriptions using TypeScript
  • Storybook story showing common use cases
  • Usage examples in documentation
/**
 * SSButton - Primary button component with multiple variants
 * 
 * @example
 * <SSButton 
 *   label="Send Bitcoin" 
 *   variant="gradient"
 *   onPress={handleSend}
 * />
 */
export type SSButtonProps = {
  /** Button text label */
  label: string
  /** Visual style variant */
  variant?: 'default' | 'secondary' | 'outline' | 'ghost' | 'gradient' | 'danger'
  /** Show loading spinner */
  loading?: boolean
}

Resources