rn-custom-ui
v1.1.0
Published
custom react native ui
Maintainers
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-uiPeer 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-webviewQuick 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 androidEach 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
