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

@dev-tech/uchat-chat-input

v0.4.6

Published

WhatsApp-style chat input for React Native: emoji & sticker keyboard, attachment menu, photo editor, voice button

Readme

@dev-tech/uchat-chat-input

WhatsApp-style chat input for React Native.

  • Emoji keyboard that swaps with the system keyboard at the same height. The input bar stays put while one slides over the other. About 1,640 emoji in Unicode order, with recents, categories, search, skin tones (long press) and whole-emoji backspace (flags, skin tones, joined emoji)
  • Stickers: offline emoji packs, or your own image URLs
  • Attachment menu: document, camera, gallery, audio
  • Photo editor: crop and rotate, draw, text, emoji stickers, per-photo captions, view once
  • Send / hold-to-record mic button: slide left to cancel, slide up to lock for hands-free recording; plug in your own recorder

Installation

yarn add @dev-tech/uchat-chat-input

Peer dependencies

Install any your app doesn't already have:

yarn add react-native-svg react-native-safe-area-context \
  react-native-keyboard-controller react-native-reanimated \
  react-native-view-shot

| Package | Used for | | --- | --- | | react-native-keyboard-controller (≥ 1.12) | tracking the keyboard frame by frame | | react-native-reanimated (≥ 3) | animating the space under the input | | react-native-svg | icons | | react-native-safe-area-context | bottom inset | | react-native-view-shot | saving edited photos | | react-native-image-picker (optional) | defaultPickers: camera and gallery | | @react-native-documents/picker (optional) | defaultPickers: documents and audio |

The two pickers are only needed if you use defaultPickers. Don't install @react-native-documents/picker alongside the older react-native-document-picker. Both register a native module named RNDocumentPicker. If your app uses the old one, pass your own pickers instead (see below).

Then rebuild the native app: cd ios && pod install, then yarn android / yarn ios.

Setup (once per app)

1. Babel plugin. Reanimated needs its Babel plugin, listed last:

// babel.config.js
module.exports = {
  presets: ['module:@react-native/babel-preset'],
  plugins: ['react-native-worklets/plugin'], // Reanimated 4
  // plugins: ['react-native-reanimated/plugin'], // Reanimated 3
};

2. KeyboardProvider at the root:

import { KeyboardProvider } from 'react-native-keyboard-controller';

<SafeAreaProvider>
  <KeyboardProvider enabled={false}>
    <App />
  </KeyboardProvider>
</SafeAreaProvider>;

enabled={false} keeps Android's normal adjustResize behaviour on every other screen. ChatInput switches keyboard tracking on while it's mounted and restores the previous setting when it unmounts.

3. Restart Metro with a clean cache after adding the Babel plugin:

yarn start --reset-cache

Android: keep android:windowSoftInputMode="adjustResize" on your activity. Don't wrap ChatInput in a KeyboardAvoidingView; it reserves the keyboard's space itself.

Usage

import { ChatInput, Message } from '@dev-tech/uchat-chat-input';
import { defaultPickers } from '@dev-tech/uchat-chat-input/src/pickers';

export function ChatScreen() {
  const send = (message: Message) => {
    // upload / append to your list
  };

  return (
    <View style={{ flex: 1 }}>
      <MessageList style={{ flex: 1 }} />
      <ChatInput
        recipientName="Stephen"
        onSendMessage={send}
        pickers={defaultPickers}
      />
    </View>
  );
}

Put ChatInput last in a full-height column. It sizes the space under itself for the keyboard, the emoji panel and the bottom safe-area inset.

What onSendMessage receives

| type | Fields | | --- | --- | | text | text | | attachment | attachment (kind: image / document / audio, uri, name, mimeType, size, width, height), text = caption, viewOnce | | sticker | sticker (emoji or image) | | voice | duration (seconds), attachment (kind: 'audio') when a recorder is given |

Picked photos open in the photo editor first. Unedited photos are sent as the original file. Edited photos arrive as a new JPEG.

Props

| Prop | Description | | --- | --- | | onSendMessage | Required. Called for every text, file, sticker or voice note | | recipientName | Required. Shown on the photo editor's send row | | value, onChangeText | Control the text yourself (needed for @mentions). Omit both and the input keeps its own text | | inputRef | Your own TextInput ref | | onSelectionChange, onFocus | Forwarded to the TextInput | | header | Rendered inside the input above the text, e.g. a reply preview | | pickers | { openCamera, openGallery, pickDocument, pickAudio }, each returning Promise<Attachment[]> ([] when cancelled). Without it the clip and camera buttons are hidden | | recorder | Your audio recorder, { start, stop, cancel } (see below). The input keeps its own recording UI (timer, waveform, slide to cancel) and calls these. Without it, recording is only simulated | | voiceButton | Replaces the whole mic button and its recording UI while the input is empty | | stickers | Show the sticker tab (default true) | | emojis | Show the emoji tab (default true). With only one of emojis / stickers on, the panel shows just that one without tabs; with both off the emoji button is hidden | | stickerSize | Sticker width and height in dp (default 64, never larger than its cell) | | stickerColumns | Stickers per row (default 4) | | viewOnce | Show the view-once toggle in the photo editor (default true) | | barStyle | Style merged over the input bar's default (12 sides, 6 top, 0 bottom — the bottom is reserved separately), e.g. { paddingHorizontal: 6 } | | minBottomInset | Least space under the bar when the keyboard and panels are closed; the larger of this and the safe-area inset is used (default 6) | | keyboardGap | Space kept between the bar and the system keyboard's top edge, like WhatsApp's thin gap (default 8) | | emojiStorage | { getItem, setItem } (sync or async, e.g. AsyncStorage or MMKV). Remembers recent emoji and chosen skin tones across app restarts; in memory otherwise | | placeholder | Default "Message" |

Using your own pickers

const pickers: AttachmentPickers = {
  openCamera: async () => toAttachments(await myCamera()),
  openGallery: async () => toAttachments(await myGallery()),
  pickDocument: async () => toAttachments(await myFilePicker()),
  pickAudio: async () => toAttachments(await myFilePicker({ audio: true })),
};

Using your own recorder

const recorder: VoiceRecorderAdapter = {
  start: () => myRecorder.start(),
  // Resolve to null if nothing usable was recorded
  stop: async () => {
    const file = await myRecorder.stop();
    return file && { uri: file.uri, duration: file.seconds, mimeType: 'audio/m4a' };
  },
  cancel: () => myRecorder.cancel(),
};

<ChatInput recorder={recorder} onSendMessage={send} recipientName="Stephen" />;

Hold the mic to record, release to send, slide left to cancel. Slide up to the lock to keep recording without holding; the bar then shows a delete button and the mic turns into Send. A tap shorter than a second is cancelled and shows "Hold to record, release to send". The recording arrives as a voice message with duration and an audio attachment.

Emoji search, skin tones and recents

  • Search: the 🔍 button on the emoji panel opens a search field with a row of results above the keyboard. Results match names and keywords and insert into the message; closing the keyboard returns to the emoji panel.
  • Skin tones: emoji with a small corner mark take a skin tone. Long press to pick one; the grid then shows that emoji in your tone.
  • Recents and tones survive restarts when you pass emojiStorage:
// uchat: MMKV. Create it once (outside the component)
import {globalStorage} from '@dev-tech/uchat-shared-lib/src/hooks/common/useStorage';

const emojiStorage = {
  getItem: (key: string) => globalStorage.getString(key),
  setItem: (key: string, value: string) => globalStorage.set(key, value),
};

<ChatInput emojiStorage={emojiStorage} ... />;

Other exports

MessageBubble, StickerView, MediaEditor, MediaViewer, EmojiKeyboard, EmojiPicker, EmojiSearch, StickerPicker, AttachmentMenu, useEmojiKeyboard, insertAtSelection, deleteBackward, searchEmojis, withSkinTone, setEmojiStorage, STICKER_PACKS, and the types Message, Attachment, AttachmentPickers, EmojiStorage, Sticker, ChatInputProps.

Emoji names and keywords are adapted from the rn-emoji-keyboard dataset (MIT, © The Widlarz Group); the package doesn't depend on it.

Using it in uchat

integration/uchat/MessagesScreen.tsx is uchat's MessagesScreen with its input replaced by ChatInput. It keeps the existing handleSendMessage, @mentions, ReplyBubble and VoiceRecorder. Before shipping it, check toUchatAttachments against what the old DocumentPicker sent.

Troubleshooting

Cannot read property 'KeyboardProvider' of undefined or Failed to create a worklet. Metro is serving code built without the Babel plugin, or the app binary predates the native packages. Stop Metro, then run yarn start --reset-cache. If it persists, rebuild the app.

... doesn't seem to be linked. The native app wasn't rebuilt after installing the peer dependencies.

Development

This repo is also a demo app (App.tsx → src/screens/ChatScreen.tsx).

yarn install
yarn start --reset-cache
yarn android            # or: cd ios && pod install && yarn ios
yarn test

Publishing

npm login               # as a member of the dev-tech org
npm version patch       # or minor / major
npm publish             # builds lib/ via prepack