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

rn-custom-ui

v1.1.0

Published

custom react native ui

Readme

rn-custom-ui

A React Native UI component library with reusable components, utility functions, and API hooks. Built for production apps that need consistent buttons, forms, lists, modals, tabs, and common helpers without extra dependencies.

Installation

npm install rn-custom-ui
# or
yarn add rn-custom-ui

Peer dependencies

| Package | Required | Notes | |---|---|---| | react | Yes | | | react-native | Yes | | | @shopify/flash-list | Optional | Only needed for CustomList with engine="flashList" | | react-native-webview | Optional | Needed for CustomHighChart (WebView + Highcharts) |

yarn add @shopify/flash-list
# for CustomHighChart
yarn add react-native-webview

Quick start

import {
  CustomButton,
  CustomCheckbox,
  CustomModal,
  formatCurrency,
  useApi,
} from 'rn-custom-ui';

Example app

An interactive showcase app lives in the example/ folder:

yarn example ios
# or
yarn example android

Each component and helper has a dedicated showcase screen with live demos.


UI Components

CustomButton

A flexible button with variants, sizes, icons, images, loading state, badge count, and icon-only mode.

Variants: default, primary, danger, warning, info, success, secondary, light, dark, link, outline, ghost, text

Sizes: sm, md, lg

| Prop | Type | Default | Description | |---|---|---|---| | variant | ButtonVariant | 'default' | Color theme | | size | 'sm' \| 'md' \| 'lg' | 'md' | Button size | | text | string | 'Submit' | Label text | | children | ReactNode | — | Custom content instead of text | | contentAlign | 'left' \| 'center' \| 'right' | 'center' | Icon + text alignment | | iconOnly | boolean | false | Square icon-only button | | iconOnlyShape | 'square' \| 'circle' | 'square' | Shape when iconOnly | | LeftIcon / RightIcon | IconComponent | — | Custom icon components | | leftImage / rightImage | ImageSourcePropType | — | Image icons | | isLoading | boolean | false | Shows loader overlay | | disabled | boolean | false | Disables press | | showCount | boolean | false | Shows badge | | itemCount | number | — | Badge value | | borderRadius | number | 0 | Corner radius | | accessibilityLabel | string | — | Required for iconOnly |

<CustomButton
  text="Save"
  variant="primary"
  borderRadius={8}
  LeftIcon={PlusIcon}
  onPress={handleSave}
/>

<CustomButton
  iconOnly
  iconOnlyShape="circle"
  variant="outline"
  LeftIcon={ShareIcon}
  accessibilityLabel="Share"
  onPress={onShare}
/>

CustomDropDown

Dropdown selector with multiple presentation modes and image support.

Modes: modal, inline, anchored, bottomSheet

| Prop | Type | Default | Description | |---|---|---|---| | mode | DropDownMode | 'modal' | How the list opens | | label | string | — | Field label | | selectedText | string | — | Currently selected value | | listItems | string[] \| DropDownOption[] | — | Options list | | onSelect | (item, index) => void | — | Selection callback | | placeholder | string | — | Placeholder when empty | | disabled | boolean | false | Disable interaction | | itemImageAlign | 'left' \| 'right' | — | Image position in list items | | toggleImageAlign | 'left' \| 'right' | — | Image on toggle button | | maxListHeight | number | — | Max height for list |

<CustomDropDown
  mode="bottomSheet"
  label="Country"
  selectedText={country}
  listItems={['Canada', 'USA', 'UK']}
  onSelect={(item) => setCountry(item)}
/>

CustomCheckbox

Checkbox with variants, sizes, shapes, label positions, indeterminate state, and error handling.

Variants: default, primary, success, danger, warning, outline

Sizes: sm, md, lg | Shapes: square, rounded, circle

| Prop | Type | Default | Description | |---|---|---|---| | checked | boolean | — | Checked state | | onChange | (checked) => void | — | Change handler | | indeterminate | boolean | false | Partial selection (dash) | | label | string | — | Label text | | description | string | — | Subtitle below label | | children | ReactNode | — | Custom label content | | labelPosition | 'left' \| 'right' | 'right' | Label side | | error | boolean | false | Error state | | errorMessage | string | — | Error text | | disabled | boolean | false | Disable interaction |

<CustomCheckbox
  variant="primary"
  label="Accept terms"
  description="You agree to our privacy policy."
  checked={accepted}
  onChange={setAccepted}
/>

CustomList

List component powered by FlatList or optional FlashList with built-in loading, empty, refresh, infinite scroll, headers/footers, and grid support.

| Prop | Type | Default | Description | |---|---|---|---| | data | T[] | — | List data | | renderItem | ListRenderItem<T> | — | Item renderer | | engine | 'flatList' \| 'flashList' | 'flatList' | List engine | | horizontal | boolean | false | Horizontal list | | numColumns | number | — | Grid layout | | loading | boolean | false | Initial loading state | | loadingMore | boolean | false | Footer loader | | refreshing | boolean | false | Pull-to-refresh | | onRefresh | () => void | — | Refresh handler | | onEndReached | () => void | — | Infinite scroll | | emptyTitle | string | — | Empty state title | | emptyDescription | string | — | Empty state message |

<CustomList
  data={items}
  engine="flashList"
  estimatedItemSize={72}
  loading={isLoading}
  onRefresh={refetch}
  refreshing={isRefreshing}
  renderItem={({ item }) => <ItemRow item={item} />}
/>

CustomAnimatedList

Animated wrapper around CustomList with predefined scroll effects ready for production feeds and notification-style UIs.

Animation presets: notification, fadeInUp, scaleIn, depth, slideInRight, accordion, float, tilt, none

| Prop | Type | Default | Description | |---|---|---|---| | animationPreset | AnimatedListPreset | 'notification' | Predefined animation style | | itemHeight | number | 74 | Used to calculate animation interpolation | | animationDistance | number | 26 | Translate intensity | | animationScaleMin | number | 0.94 | Minimum scale during animation | | animationOpacityMin | number | 0.55 | Minimum opacity during animation |

Supports all CustomList props like engine, showSeparator, onRefresh, onEndReached, and numColumns.

<CustomAnimatedList
  data={notifications}
  keyExtractor={(item) => item.id}
  animationPreset="notification"
  itemHeight={92}
  showSeparator
  renderItem={({ item }) => <NotificationCard item={item} />}
/>

CustomCarousel

Horizontal carousel built on FlatList or optional FlashList, with paging dots, autoplay, snapping, animation types, and style variants.

Animations: none, scale, parallax, stack, fade
Style variants: default, card, peek, stacked

| Prop | Type | Default | Description | |---|---|---|---| | engine | 'flatList' \| 'flashList' | 'flatList' | Carousel list engine | | animationType | CarouselAnimationType | 'scale' | Slide animation | | styleVariant | CarouselStyleVariant | 'default' | Item visual style | | showPagingDots | boolean | true | Show page indicators | | autoPlay | boolean | false | Auto-scroll items | | autoPlayInterval | number | 3000 | Autoplay interval (ms) | | loop | boolean | false | Loop at end | | itemWidth | number | — | Custom item width | | itemGap | number | 12 | Space between items | | sidePeek | number | 0 | Visible side preview |

<CustomCarousel
  data={slides}
  engine="flashList"
  animationType="parallax"
  styleVariant="peek"
  autoPlay
  loop
  keyExtractor={(item) => item.id}
  renderItem={({ item }) => <PromoCard item={item} />}
/>

CustomHighChart

Highcharts renderer for React Native using WebView + HTML template with callback-driven chart config, dynamic data, and full width/height control.

| Prop | Type | Default | Description | |---|---|---|---| | data | TData | — | Input data for your chart | | buildChartOptions | (args) => HighChartOptions | — | Callback that returns Highcharts options | | width | number \| string | '100%' | Chart container width | | height | number | 300 | Chart height | | theme | 'light' \| 'dark' | 'light' | Theme passed to callback | | backgroundColor | string | '#ffffff' | Chart background | | onChartMessage | (message: string) => void | — | Receives WebView chart events/errors |

buildChartOptions receives { data, width, height, theme }, so you can generate line, area, column, pie, donut, stacked, or mixed charts dynamically.

<CustomHighChart
  data={salesData}
  width="100%"
  height={340}
  theme="dark"
  buildChartOptions={({ data, width, height, theme }) => ({
    chart: { type: 'line', width, height, backgroundColor: theme === 'dark' ? '#12161f' : '#fff' },
    xAxis: { categories: data.labels },
    series: [{ name: 'Revenue', data: data.values }],
    credits: { enabled: false },
  })}
/>

CustomNumberPad

Numeric keypad for PIN entry, amounts, and custom numeric input.

| Prop | Type | Default | Description | |---|---|---|---| | value | string | — | Controlled value | | onChange | (value) => void | — | Value change handler | | maxLength | number | — | Max digits | | allowDecimal | boolean | false | Show decimal key | | decimalPlaces | number | — | Max decimal digits | | variant | 'default' \| 'primary' \| 'dark' | 'default' | Theme | | size | 'sm' \| 'md' \| 'lg' | 'md' | Key size | | bottomLeftKey | { label, onPress } | — | Custom key (e.g. Clear) | | disabled | boolean | false | Disable all keys |

Hook: useNumberPadInput — manages value state with built-in validation.

const pinPad = useNumberPadInput({ maxLength: 4 });

<CustomNumberPad {...pinPad.numberPadProps} />
<Text>{pinPad.value}</Text>

CustomModal

Modal dialog with center, bottom sheet, and fullscreen presentations. Includes footer actions and close button.

Presentations: center, bottom, fullscreen

| Prop | Type | Default | Description | |---|---|---|---| | visible | boolean | — | Show/hide modal | | onClose | () => void | — | Close handler | | title | string | — | Header title | | description | string | — | Header subtitle | | presentation | ModalPresentation | 'center' | Layout style | | scrollable | boolean | false | Scroll body content | | showCloseButton | boolean | true | X button in header | | closeOnBackdropPress | boolean | true | Tap outside to close | | primaryAction | ModalAction | — | Primary footer button | | secondaryAction | ModalAction | — | Secondary footer button | | footer | ReactNode | — | Custom footer |

Hook: useCustomModal{ visible, open, close, toggle }

const modal = useCustomModal();

<CustomButton text="Delete" variant="danger" onPress={modal.open} />

<CustomModal
  visible={modal.visible}
  onClose={modal.close}
  title="Delete item?"
  description="This action cannot be undone."
  secondaryAction={{ label: 'Cancel', onPress: modal.close }}
  primaryAction={{ label: 'Confirm', variant: 'danger', onPress: handleDelete }}
/>

CustomSlideTabs

Top tab bar with swipeable scenes, animated sliding indicator, per-tab API loading, and customizable headings.

| Prop | Type | Default | Description | |---|---|---|---| | tabs | SlideTabItem[] | — | Tab config (key, title, heading, subtitle, image, badge) | | renderScene | (state) => ReactNode | — | Screen content per tab | | fetchSceneData | (tab, index, signal) => Promise | — | API call when tab is active | | lazy | boolean | true | Mount scenes on first visit | | fetchWhenActive | boolean | true | Fetch only when tab is visible | | refetchOnRevisit | boolean | false | Re-fetch every time tab is opened | | animatedIndicator | boolean | true | Sliding pill + underline | | renderTab | (tab, index, isActive) => ReactNode | — | Custom tab label | | renderSceneHeader | (state) => ReactNode | — | Custom scene heading | | onTabChange | (index, tab) => void | — | Tab change callback |

renderScene receives: data, error, isLoading, isSuccess, isError, refetch, isActive, tab, index.

<CustomSlideTabs
  tabs={[
    { key: 'all', title: 'All', heading: 'All calls', subtitle: 'Every call' },
    { key: 'missed', title: 'Missed', heading: 'Missed calls' },
  ]}
  fetchSceneData={(tab) => fetchCalls(tab.key)}
  renderScene={({ tab, data, isLoading }) => (
    <CallList data={data} loading={isLoading} />
  )}
/>

Custom Functions

Dependency-free utility helpers exported from the main package.

Array

| Function | Description | |---|---| | chunk(items, size) | Split array into batches | | flatten(items) | Flatten one level of nesting | | groupBy(items, getKey) | Group items by key | | shuffle(items) | Randomize order | | sortBy(items, selector, direction?) | Sort by selector ('asc' / 'desc') | | uniqueBy(items, selector) | Deduplicate by key |

Async

| Function | Description | |---|---| | debounce(fn, delayMs) | Delay execution (includes .cancel()) | | throttle(fn, intervalMs) | Limit execution rate | | sleep(ms) | Promise-based delay | | tryParseJson(value, fallback?) | Safe JSON.parse | | safeStringify(value, space?, fallback?) | Safe JSON.stringify |

Date

| Function | Description | |---|---| | addDays(date, days) | Add days to a date | | differenceInDays(a, b) | Day difference between dates | | differenceInMinutes(a, b) | Minute difference | | formatDateYYYYMMDD(date) | Format as YYYY-MM-DD | | formatDateTimeLocalized(date, options?) | Localized date/time string | | formatRelativeTime(date) | "5 min ago", "in 2 hours" | | formatTimestampTo12Hour(timestamp) | 12-hour time string | | isToday(date) | Check if date is today | | isSameDay(a, b) | Compare calendar days | | isOlderThanHours(date, hours) | Check if date is older than N hours | | parseCompactDateTime(value) | Parse compact date string | | generateCompactTimestampId() | Timestamp-based ID | | getIsoTimeWithoutMilliseconds() | ISO time without ms |

General

| Function | Description | |---|---| | isDefined(value) | Type guard for non-null/undefined | | isEmpty(value) | Check empty string/array/object | | isNullish(value) | Check null or undefined | | logger(...args) | Dev-friendly console logger | | noop() | Empty function placeholder |

ID

| Function | Description | |---|---| | generateUuid() | Random UUID | | generateSessionId() | Session identifier | | generateRandomAlphanumeric(length?) | Random alphanumeric string |

Number

| Function | Description | |---|---| | formatNumberWithCommas(value, options?) | 1250000"1,250,000" | | formatCurrency(amount, options?) | Currency formatting | | formatPercentage(value, decimals?) | 42.5"42.5%" | | formatBytes(bytes) | 1536"1.5 KB" | | formatDiscountAmount(amount) | Discount display format | | parseNumberSafe(value) | Parse number, fallback to 0 | | percentage(part, total) | Calculate percentage | | sum(values) / average(values) | Array math | | clamp(value, min, max) | Clamp number in range | | roundToDecimals(value, decimals) | Round to N decimals | | randomInt(min, max) | Random integer in range |

Object

| Function | Description | |---|---| | pick(object, keys) | Select keys from object | | omit(object, keys) | Exclude keys from object | | deepClone(value) | JSON-safe deep copy | | getNestedValue(object, path, fallback?) | Read "user.address.city" | | isPlainObject(value) | Type guard for plain objects |

String

| Function | Description | |---|---| | truncate(text, maxLength, suffix?) | Ellipsis truncation | | capitalizeFirst(text) | Capitalize first letter | | capitalizeWords(text) | "hello world""Hello World" | | camelToTitle(text) | "firstName""First Name" | | slugify(text) | URL-friendly slug | | maskString(value, options?) | Mask sensitive strings | | normalizeWhitespace(text) | Trim and collapse spaces | | initialsFromName(name) | "Jane Doe""JD" | | removeDuplicates(items) | Dedupe array | | removeDuplicateStrings(items) | Dedupe string array | | findItemByProperty(items, key, value) | Find item in array | | getLocalizedSplitText(text, locale?) | Locale-aware text split |

Validation

| Function | Description | |---|---| | isValidEmail(email) | Email format check | | isValidUrl(url) | URL format check | | isValidPhone(phone) | Phone format check | | isNumeric(value) | Numeric string check | | isStrongPassword(password) | Password strength rules | | encodePlusInEmail(email) | Encode + in email |

Backward-compatible aliases

Legacy names from app helpers are also exported: getFrAmount, getFirstCap, getFormattedDate, getCurrentTime, generateCartSessionId, getTextByLanguage, and others.


Custom API Hooks

React hooks for data fetching with loading, error, and success states. No external dependencies.

| Hook | Use case | |---|---| | useApi | Auto-fetch on mount (GET) | | useLazyApi | Manual fetch (search, button tap) | | useMutation | POST / PUT / DELETE actions | | usePaginatedApi | Infinite scroll / pagination | | usePollingApi | Auto-refresh on interval |

Utilities

| Export | Description | |---|---| | ApiError | Standard error with status, code, details | | toApiError(error) | Normalize any error to ApiError | | getApiErrorMessage(error) | Human-readable error message | | fetchJson(url, options?) | Typed fetch wrapper |

const { data, isLoading, isError, error, refetch } = useApi(
  () => fetchJson<User[]>('/api/users'),
  { deps: [userId] }
);

const saveUser = useMutation((payload: CreateUserInput) =>
  fetchJson('/api/users', { method: 'POST', body: JSON.stringify(payload) })
);

TypeScript

Full TypeScript support. Import types alongside components:

import {
  CustomButton,
  type ButtonVariant,
  type CustomModalProps,
  type SlideTabItem,
} from 'rn-custom-ui';

Contributing

License

MIT


Made with create-react-native-library