Five Themeable UI Primitives for React Native — Badge, Button, Icons, Input & Skeleton

in Steem Dev4 days ago

Previously: Launching the rn-markdown-editor ecosystem — a native MarkdownEditor, MarkdownRenderer, and MarkdownTable suite for React Native and Expo.

A short rundown of the core building-block components included in this package, what each one actually does, and how the pieces fit together.


Overview

This package ships a small set of themeable, cross-platform primitives for React Native (iOS, Android, and Web). Every component reads its colors from a shared useColors() theme hook, so swapping a theme updates every component consistently — no per-component color props to chase down unless you want to override them.

The components covered here:

  • Badge — small status/label pill
  • Button — interactive pressable with variants and hover/press states
  • Icons — a set of 41 hand-built SVG icons
  • Input — themed single/multiline text field
  • Skeleton — animated loading placeholder
  • ImageViewer — full-screen pinch-zoom image viewer with download support

Badge

Badge renders a small rounded pill, typically used for statuses, counts, or tags.

  • 7 variants: default, secondary, outline, destructive, success, accent, warning — each maps to a different background/text pair from the active theme.
  • Manual overrides: bgColor, textColor, and borderColor props let you bypass the variant defaults entirely for a one-off badge.
  • Render-prop children: pass a function as children and receive the resolved textColor, useful when you need to color an icon or nested text to match.
  • Built with forwardRef, so a parent can hold a ref to the underlying View.
<Badge variant="success">Active</Badge>
<Badge variant="outline" borderColor="#999">Draft</Badge>

Button

Button wraps Pressable with five visual variants (primary, secondary, outline, ghost, link) and five size presets (default, sm, lg, icon, none).

  • Hover and press states are tracked internally (isHovered, isPressed) and each variant defines its own default background/text/border for the resting, hovered, and pressed states — all independently overridable via props like hoveredBackgroundColor or pressedTextColor.
  • outline variant inverts to a filled, theme-colored button on hover/press rather than just changing the border.
  • link variant underlines its text on hover.
  • Accepts a render-prop children function exposing { hovered, pressed, textColor, backgroundColor } for fully custom button contents.
  • Disabled state automatically drops opacity to 0.5.

Icons

A set of 41 SVG icons built directly on react-native-svg — no icon font, no @expo/vector-icons dependency.

  • Every icon is generated through a single makeIcon() factory, so they all share the same prop surface: size, color, fill, and strokeWidth.
  • Color defaults to the theme's foreground token via useColors() if no explicit color is passed.
  • Covers common needs for a text editor / content UI: formatting (Bold, Italic, Underline, Strikethrough, Code), headings (Heading1–3), lists, alignment, media (Link, Image, Table, Upload), and general UI icons (ChevronLeft/Right/Up/Down, Cross, Check, Search, Settings, Bell, Heart, and more).
<Icons.Bold size={18} />
<Icons.Heart color={colors.destructive} fill={colors.destructive} />

Input

A themed wrapper around React Native's TextInput.

  • Focus-aware border: switches to the theme's ring color while focused, border color otherwise.
  • Auto-growing multiline: when multiline is set, the component tracks onContentSizeChange and adjusts its own height to fit content instead of relying on a fixed numberOfLines.
  • Web outline removal: strips the default browser focus outline on web so the themed border is the only focus indicator.
  • Forwards its ref to the underlying native TextInput, so .focus() / .blur() work as expected from a parent.

Skeleton

A loading placeholder that pulses opacity between 1 and 0.4 on a 700ms loop, using Animated with useNativeDriver: true.

  • Configurable width, height, and borderRadius.
  • The animation starts on mount and is cleaned up (pulse.stop()) on unmount, so it won't keep animating after the component is gone.

Here's what that pulse looks like in practice:

Skeleton Loading

ImageViewer

A full-screen modal image viewer with pinch-to-zoom, pan, and a save-to-device button.

  • Pinch-to-zoom & pan: implemented with raw responder events (onResponderGrant/Move/Release) and Animated.Value tracking — clamped between 0.5x and 5x scale. Releasing below 1x springs back to the reset position.
  • Web support: mouse-wheel zoom is wired up separately for web, since there's no pinch gesture there.
  • Download flow: on native, it requests expo-media-library permission, downloads via expo-file-system's DownloadTask with live progress, and saves directly to the camera roll. On web, it fetches the image as a blob and triggers a real <a download> save (falling back to a plain anchor click if the fetch fails, e.g. due to CORS).
  • Imperative API: exposes open(src, alt?) and close() via useImperativeHandle, so you control it from a ref rather than local visibility state.
  • Skips mounting the heavy modal subtree entirely while closed, and guards against overlapping open/close animations.
const viewerRef = useRef<ImageViewerRef>(null);
viewerRef.current?.open(imageUrl, "A description of the image");

<ImageViewer ref={viewerRef} accentColor="#3B82F6" />

System Reliability & Support

The development team remains committed to maintaining a secure, efficient, and highly optimized open-source toolkit. Continuous updates are actively deployed to ensure total cross-platform stability across iOS, Android, and Web deployments. Please cross-reference your project environments and peer packages carefully during initial setup.

For technical assistance or to report rendering anomalies, please submit an issue on our official NPM Project Page.

Thank you for your continued support as we optimize the mobile markdown environment.


Thanks for your time and support. Let’s make Steem great, again.

Learn More About rn-markdown-editor:


Official NPM Link: https://www.npmjs.com/package/rn-markdown-editor
Primary Architecture: TypeScript / JavaScript
Supported Environments: Expo & Bare React Native CLI
Utility Exports: useDebouncedInput, useDisclosure, useMergeState