npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

movius-chats

v1.4.5

Published

A highly customizable, feature-rich chat interface component for React Native applications

Readme

movius-chats

A customizable React Native chat UI library. One ChatScreen component: message bubbles, WhatsApp-style media grids, audio playback, voice recording, composer previews, typing indicators, and a full-screen media gallery — with per-side theming and replaceable UI pieces.

npm: movius-chats
Repo: github.com/David-Atueyi/Movius-Chats

This package is built for plain React Native (CLI / bare workflow). It does not depend on any Expo module.


Table of contents

  1. What is included
  2. Package layout
  3. Dependencies
  4. Installation
  5. Quick start
  6. Message data model
  7. Message list order
  8. ChatScreen API
  9. Voice recording
  10. Audio message bubbles
  11. Media grids & gallery
  12. Composer attachment preview
  13. Theme & styling
  14. Keyboard behavior
  15. Custom components & icons
  16. TypeScript
  17. Troubleshooting
  18. Publishing
  19. License

What is included

| Feature | Implementation | |---------|----------------| | Text messages | react-native-parsed-text (URLs tappable) | | Image / video albums | MediaGrid — 1 / 2 / 3 / 4+ layout, 320px height | | Full-screen viewer | MediaViewer — swipe, counter, selective video autoplay | | Audio messages | react-native-video (hidden player) + waveform UI | | Playback speed | 1x → 1.5x → 2x while playing | | Voice recording | react-native-audio-record (optional peer) | | File attachments | Tappable rows; default Linking.openURL | | Typing indicator | Up to 2 avatars + +N badge | | Input bar | Growing text field, emoji / clip / camera / send / mic | | Status icons | Sent / delivered / read checkmarks | | Theming | Separate sent* / received* colors for most bubble parts |


Package layout

src/
├── index.tsx                 # ChatScreen entry
├── types/index.ts            # Message, ChatScreenProps, recorder types
├── context/
│   ├── ChatContext.tsx       # Props + gallery state
│   └── AudioContext.tsx      # One audio plays at a time
├── hooks/
│   ├── useKeyboardInset.ts   # Keyboard height → input margin
│   └── useVoiceRecorder.ts   # Mic capture (audio-record + fs)
├── utils/
│   ├── bubbleTheme.ts        # sent/received color helpers
│   ├── messageMedia.ts       # collectMediaItems()
│   ├── theme.ts              # fontFamily, input icon size
│   └── datefunc.ts           # formatDuration()
├── assets/Icons/             # SVG icons (play, mic, tail, etc.)
└── components/
    ├── ChatBubble/           # Bubble, content, status, media grid
    ├── ChatInput/            # Input, FilePreview, voice gestures
    ├── AudioPlayer/          # WhatsApp-style audio UI
    ├── MediaViewer/          # Full-screen gallery modal
    ├── TypingComponent/
    └── VoiceRecorder/        # Normal + long-press recording UI

Published build output: lib/commonjs, lib/module, lib/typescript.


Dependencies

Bundled (installed with movius-chats)

| Package | Use | |---------|-----| | react-native-video | Video thumbnails, gallery video, audio playback | | react-native-svg | Built-in icons | | react-native-parsed-text | Link detection in text | | twrnc | Internal styles |

Peer dependencies (your app must install)

| Package | Required | |---------|----------| | react ≥ 16.8 | Yes | | react-native | Yes | | react-native-reanimated | Yes (voice recorder animations) |

Optional peers (voice recording only)

| Package | Use | |---------|-----| | react-native-audio-record | Record microphone | | react-native-fs | Delete cancelled recording files |

If these are missing, the UI still renders; starting a recording logs an install hint.

There is no react-native-sound, expo-av, expo-file-system, or other Expo package in this library.


Installation

1. Install movius-chats

yarn add movius-chats
# or: npm install movius-chats
# or: bun add movius-chats

2. Install peers

yarn add react-native-reanimated react-native-video react-native-svg

react-native-video and react-native-svg are also pulled in as movius-chats dependencies, but your app should list compatible versions and link native code.

3. Reanimated (Babel)

Put this plugin last in babel.config.js:

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: [
    // ...other plugins
    'react-native-reanimated/plugin',
  ],
};

4. Voice recording (optional)

yarn add react-native-audio-record react-native-fs

iOS — add to Info.plist:

<key>NSMicrophoneUsageDescription</key>
<string>This app needs the microphone to record voice messages.</string>

Android — ensure RECORD_AUDIO is in AndroidManifest.xml (often added by the audio-record library).

Then rebuild native apps:

cd ios && pod install && cd ..
npx react-native run-ios
npx react-native run-android

5. Android keyboard

In android/app/src/main/AndroidManifest.xml on your main activity:

android:windowSoftInputMode="adjustResize"

Quick start

import React, { useState } from 'react';
import { Platform, SafeAreaView, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import ChatScreen from 'movius-chats';
import type { Message } from 'movius-chats/lib/typescript/types';

export default function ChatDetailScreen() {
  const insets = useSafeAreaInsets();
  const currentUserId = '1';
  const [messages, setMessages] = useState<Message[]>([]);

  return (
    <SafeAreaView style={{ flex: 1 }}>
      <View style={{ flex: 1 }}>
        <ChatScreen
          messages={messages}
          currentUserId={currentUserId}
          onSendMessage={({ text, senderId }) => {
            setMessages((prev) => [
              {
                id: String(Date.now()),
                text,
                senderId,
                time: new Date().toLocaleTimeString([], {
                  hour: '2-digit',
                  minute: '2-digit',
                }),
                status: 'sent',
              },
              ...prev,
            ]);
          }}
          keyboardVerticalOffset={Platform.OS === 'ios' ? insets.top : 0}
          showBubbleTail
          showMessageStatus
          showVoiceRecordButton
        />
      </View>
    </SafeAreaView>
  );
}

Wrap the screen in flex: 1. Load custom fonts in your app before passing theme.fontFamily.


Message data model

Message

| Field | Type | Description | |-------|------|-------------| | id | string | Unique id | | senderId | string | Who sent it | | time | string | Display time (you format it) | | status | 'sent' \| 'delivered' \| 'read' | Checkmarks on your messages only | | text | string | Body text | | audio | string | Audio file URI | | image | string | Single image (legacy; prefer mediaItems) | | video | string | Single video (legacy) | | mediaItems | MessageMediaItem[] | Album in one bubble | | fileAttachments | MessageFileAttachment[] | PDF, doc, etc. | | senderName | string | Group name + audio avatar initial | | senderAvatar | string | Image URI for audio bubble avatar |

MessageMediaItem

{ uri: string; kind: 'image' | 'video' }

MessageFileAttachment

{ uri: string; type: string; name: string }

PreviewAttachment (composer)

{ uri: string; type: string; name?: string }

RecordingResult (onAudioRecordEnd)

{ uri: string; duration: number; mimeType?: string; size?: number }

Message list order

The list is inverted. Newest message must be index 0:

setMessages((prev) => [newMessage, ...prev]);

Avatars and bubble tails show only on the first message in a consecutive run from the same senderId.


ChatScreen API

Default export: ChatScreen. All props are optional except messages, currentUserId, and onSendMessage.

Core

| Prop | Type | Description | |------|------|-------------| | messages | Message[] | Newest first | | currentUserId | string | Sent vs received layout | | onSendMessage | (Omit<Message, 'id' \| 'time' \| 'status'>) => void | Send button | | onMessageLongPress | (message: Message) => void | Long-press bubble | | placeholder | string | Input placeholder (default "Message") | | keyboardVerticalOffset | number | iOS only — passed to KeyboardAvoidingView | | disableKeyboardAvoiding | boolean | Turn off built-in keyboard lift |

Feature flags (default false)

showAvatars, showUserNames, showBubbleTail, showMessageStatus, showEmojiButton, showAttachmentsButton, showCameraButton, showVoiceRecordButton

Callbacks

| Prop | Description | |------|-------------| | onTypingStart / onTypingEnd | Input text empty ↔ non-empty | | onAttachmentPress | Paperclip — open your picker | | onCameraPress | Camera icon | | onAudioRecordStart | Recording began | | onAudioRecordEnd | (RecordingResult?) => void when done or cancelled | | onFileAttachmentPress | File chip in bubble (default: Linking.openURL) |

Composer preview

| Prop | Description | |------|-------------| | previewItems | Multiple attachments before send | | previewData | Single attachment (legacy) | | onRemovePreviewItem | (uri) => void — remove one card by URI | | closePreview | Clears all if onRemovePreviewItem not set |

When preview or text exists, the send icon shows instead of the mic.

Voice recorder customization

| Prop | Type | |------|------| | renderVoiceRecorder | (VoiceRecorderExposedState) => ReactNode — replace entire recorder UI | | voiceRecorderProps | VoiceRecorderConfigmaxDuration, lock, slide-to-cancel, etc. | | voiceRecorderStyles | VoiceRecorderStyleOverrides | | recordingUIProps | Colors/sizes for timer, lock pill, recorder play/pause |

Typing

| Prop | Type | |------|------| | typingUsers | { id, avatar, name }[] |


Voice recording

Requires react-native-audio-record and react-native-fs in the host app, plus a native rebuild.

Gestures

| Action | Result | |--------|--------| | Tap mic | Normal bar: trash, timer, waveform, play/pause preview, send | | Long-press mic | Hold mode: “slide to cancel”, lock column above send | | Slide left | Cancel (file deleted via react-native-fs) | | Slide up to lock | Switches to normal bar (lockSlideDistance in recordingUIProps) | | Release without slide | Auto-send (onAudioRecordEnd) |

Wiring

<ChatScreen
  showVoiceRecordButton
  onAudioRecordEnd={(result) => {
    if (!result) return;
    setMessages((prev) => [
      {
        id: String(Date.now()),
        senderId: currentUserId,
        audio: result.uri,
        time: '10:56 PM',
        status: 'sent',
        senderAvatar: myAvatarUri,
        senderName: myDisplayName,
      },
      ...prev,
    ]);
  }}
/>

Custom recorder UI

renderVoiceRecorder={(state) => (
  <MyRecorder
    duration={state.duration}
    onStop={state.stopRecording}
    onCancel={state.cancelRecording}
  />
)}

Audio message bubbles

WhatsApp-style row inside the bubble:

| Side | Layout (left → right) | |------|------------------------| | Sent | Avatar or speed pill → play/pause → waveform | | Received | play/pause → waveform → avatar or speed pill |

| State | Avatar slot | |-------|-------------| | Idle / finished | senderAvatar or first letter of senderName | | Playing | Pill showing 1x, 1.5x, or 2x (tap to cycle) | | Ended | Avatar again |

  • Waveform bars with scrubber dot; tap or drag to seek
  • Duration under the waveform
  • Play/pause is icon-only (no filled circle)
  • Only one audio plays at a time (AudioContext)
  • Video in the gallery pauses other audio
{
  id: 'a1',
  senderId: '2',
  audio: 'file:///data/user/0/.../voice.wav',
  senderAvatar: 'https://cdn.example.com/u2.jpg',
  senderName: 'Alex',
  time: '10:23 pm',
  status: 'read',
}

Media grids & gallery

Grid (mediaItems)

| Count | Layout | Height | |-------|--------|--------| | 1 | Full width, cover | 320px | | 2 | Two columns | 320px | | 3 | One top, two bottom | 320px | | 4+ | 2×2, +N on last cell | 320px |

Tap opens MediaViewer. Thumbnail Video uses pointerEvents="none" so presses reach the parent.

Gallery behavior

  • Horizontal FlatList, n / total header
  • Videos play only if that video was the tapped item and the page is active
  • Tapping an image in a mixed album does not start other videos
  • Composer video previews do autoplay in the small preview card

Legacy single fields

image and video on Message are merged into mediaItems internally via collectMediaItems().


Composer attachment preview

Controlled from your app state:

const [previews, setPreviews] = useState<PreviewAttachment[]>([]);

<ChatScreen
  previewItems={previews}
  onRemovePreviewItem={(uri) =>
    setPreviews((p) => p.filter((x) => x.uri !== uri))
  }
  onAttachmentPress={openYourDocumentPicker}
  onSendMessage={handleSend}
/>

| Preview type | UI | |--------------|-----| | 1 image/video | Single thumb + × | | 2–3 media | Fanned stack, × on each | | 4+ media | Fan of 3 + +N, × per visible card | | Documents | Chips; scrollable after 3 |

Use any picker you want (react-native-document-picker, react-native-image-picker, etc.) — movius-chats only displays previewItems.


Theme & styling

Pass theme to ChatScreen. theme.fontFamily applies to all Text in the package (load the font in your app first).

theme.colors — per side (sent* / received*)

| Keys | Used for | |------|----------| | sentTimestampColor / receivedTimestampColor | Message & file timestamps | | sentMessageTextColor / receivedMessageTextColor | Bubble text | | sentBubbleBackgroundColor / receivedBubbleBackgroundColor | Bubble background | | sentMessageTailColor / receivedMessageTailColor | Corner tail (ArrowBack2RoundedIcon) | | sentFileAttachmentBackground / receivedFileAttachmentBackground | File chip | | sentFileAttachmentTextColor / receivedFileAttachmentTextColor | File name | | sentFileAttachmentSubtitleColor / receivedFileAttachmentSubtitleColor | MIME line | | sentAudioWaveformColor / receivedAudioWaveformColor | Inactive waveform bars | | sentAudioWaveformActiveColor / receivedAudioWaveformActiveColor | Active bars + scrubber | | sentAudioTimestampColor / receivedAudioTimestampColor | Duration under waveform | | sentAudioPlayIconColor / receivedAudioPlayIconColor | Play icon | | sentAudioPauseIconColor / receivedAudioPauseIconColor | Pause icon | | sentAudioSpeedTextColor / receivedAudioSpeedTextColor | 1x / 1.5x / 2x pill text | | sentMediaTimestampBackground / receivedMediaTimestampBackground | Timestamp pill on file-only bubbles |

Not themeable: image/video-only messages (no text, no audio) always use white (#ffffff) for the timestamp text.

Shared colors

inputsIconsColor, sendIconsColor, placeholderTextColor, inputTextColor, sentIconColor, deliveredIconColor, readIconColor, videoPlayIconColor

theme.sizes

inputIconSize — number (px) or twrnc class string; affects emoji, paperclip, camera only (not send/mic).

theme.bubbleStyle

sent, received, avatarTextStyle, userNameStyle, avatarImageStyle, typing styles.

theme.messageStyle

Text styles, file attachment styles, progressBarStyle, activeProgressBarStyle, audioDurationStyle, audioSpeedButtonStyle, audioSpeedTextStyle, media timestamp container styles.

theme.inputStyles / theme.filePreviewStyle

Input row, send button, preview strip.

Example

<ChatScreen
  theme={{
    fontFamily: 'Inter-Regular',
    colors: {
      sentBubbleBackgroundColor: '#005C4B',
      receivedBubbleBackgroundColor: '#1F2C34',
      sentAudioSpeedTextColor: '#FFFFFF',
      receivedAudioSpeedTextColor: '#E5E7EB',
      sentAudioWaveformActiveColor: '#53BDEB',
      receivedAudioWaveformActiveColor: '#53BDEB',
    },
    sizes: { inputIconSize: 22 },
  }}
/>

Keyboard behavior

Built into ChatScreen:

| Platform | Behavior | |----------|----------| | Android | useKeyboardInset sets marginBottom on the input row (= keyboard height) | | iOS | Same inset plus KeyboardAvoidingView with behavior="padding" and keyboardVerticalOffset |

If your navigator already avoids the keyboard:

<ChatScreen disableKeyboardAvoiding />

Custom components & icons

| Prop | Replaces | |------|----------| | renderCustomInput | Entire input + recorder (you handle preview yourself) | | renderCustomTyping | “Typing…” content | | renderCustomVideoBubbleError | Inline video error in grid | | renderVoiceRecorder | Built-in recorder bars | | CustomEmojiIcon | Emoji button | | CustomAttachmentIcon | Paperclip | | CustomCameraIcon | Camera | | CustomSendIcon | Send | | CustomMicrophoneIcon | Mic | | CustomPlayIcon / CustomPauseIcon | Audio (and related) playback | | CustomFileIcon | Document chip icon | | CustomImagePreview / CustomVideoPreview | Composer thumbnails |

File attachments without Expo

Default tap uses React Native Linking.openURL. For local files or share sheets, use your own module in the host app:

import { Linking } from 'react-native';
// or: react-native-share, react-native-blob-util, etc.

onFileAttachmentPress={async (file) => {
  const uri = file.uri.startsWith('file://') ? file.uri : `file://${file.uri}`;
  await Linking.openURL(uri);
}}

TypeScript

import ChatScreen from 'movius-chats';

import type {
  Message,
  MessageMediaItem,
  MessageFileAttachment,
  PreviewAttachment,
  RecordingResult,
  ChatScreenProps,
  VoiceRecorderExposedState,
  VoiceRecorderConfig,
  VoiceRecorderStyleOverrides,
  RecordingUIProps,
} from 'movius-chats/lib/typescript/types';

Source types while developing against the repo: movius-chats/src/types (field "react-native": "src" in package.json).


Troubleshooting

| Problem | What to do | |---------|------------| | Cannot read property 'init' of null | Install react-native-audio-record, run pod install, rebuild the app | | Recording never starts | Mic permission; iOS NSMicrophoneUsageDescription; Android RECORD_AUDIO | | No audio playback | Ensure react-native-video is linked; URI must be file:// or http(s):// | | NoSuchMethodError DefaultLoadControl (Android) | Force androidx.media3 to 1.3.1 in the app android/build.gradle resolutionStrategy | | Reanimated error | react-native-reanimated/plugin must be last in Babel plugins | | Font not applied | Register font in the host app; pass exact fontFamily string | | inputIconSize ignored on send/mic | By design | | Keyboard covers input (Android) | android:windowSoftInputMode="adjustResize"; parent flex: 1 | | × clears all previews | Implement onRemovePreviewItem | | Wrong audio avatar | Set senderAvatar and senderName on the Message | | Messages upside down | Newest at messages[0] |


License

ISC — see package.json.