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

feav-ui

v1.0.18

Published

UI Library with theme system for React Native

Readme

feav-ui

React Native UI Library with theme system for cross-platform mobile applications.

npm version License

Features

  • 🎨 Theme System: Light/Dark/Blue/Green/Purple/BW/Custom themes với 80+ colors
  • 📦 80+ Components: Buttons, Inputs, Layouts, Media, Feedback, Charts, Navigation, Calendar, DateTimePicker
  • 🔄 State Management: Built-in Zustand store với persistence
  • 📱 Cross-platform: iOS & Android ready
  • ⚡️ Lightweight: Chỉ ~184KB gzipped

Installation

npm install feav-ui
# or
yarn add feav-ui

Peer Dependencies

npm install react react-native zustand @react-native-community/slider

Quick Start

1. Setup Storage Adapter (Required)

// App.tsx
import { setStorageAdapter, useThemeStore } from 'feav-ui';
import AsyncStorage from '@react-native-async-storage/async-storage';

// Setup storage for theme persistence
setStorageAdapter({
  getItem: async (key) => await AsyncStorage.getItem(key),
  setItem: async (key, value) => await AsyncStorage.setItem(key, value),
});

// Load saved theme
useThemeStore.getState().loadTheme();

2. Use Theme Hooks

import { useTheme, useThemeColor, useChangeTheme } from 'feav-ui';

function MyComponent() {
  const theme = useTheme(); // Get all theme colors
  const primaryColor = useThemeColor('primary'); // Get single color
  const changeTheme = useChangeTheme(); // Get theme setter

  return (
    <View style={{ backgroundColor: theme.background }}>
      <Text style={{ color: theme.text }}>Hello!</Text>
      <Button title='Switch Theme' onPress={() => changeTheme('dark')} />
    </View>
  );
}

API Reference

Theme System

Hooks

| Hook | Parameters | Returns | Description | | ---------------------- | ------------------------ | --------------------------------------------------- | ----------------------- | | useTheme() | - | ThemeColors | Get all theme colors | | useThemeColor(key) | key: keyof ThemeColors | string | Get single color | | useThemeType() | - | ThemeType | Get current theme type | | useChangeTheme() | - | (theme: ThemeType) => Promise<void> | Change theme | | useSetCustomTheme() | - | (theme: Partial<ThemeColors>) => Promise<boolean> | Set custom theme | | useLoadTheme() | - | () => Promise<void> | Load theme from storage | | useAvailableThemes() | - | ThemeConfig[] | Get available themes |

Store Methods

const store = useThemeStore.getState();

store.setTheme('dark'); // Change theme
store.setCustomTheme({ primary: '#FF0000' }); // Set custom theme
store.getCurrentTheme(); // Get current theme colors
store.getAvailableThemes(); // Get theme list
store.getCustomTheme(); // Get custom theme
store.getColorKeys(); // Get all color keys
store.getFontKeys(); // Get font keys
store.loadTheme(); // Load from storage

Types

type ThemeType = 'light' | 'dark' | 'blue' | 'green' | 'purple' | 'bw' | 'custom';

interface ThemeColors {
  theme?: string;
  primary: string;
  primaryDark: string;
  primaryLight: string;
  secondary: string;
  accent: string;
  background: string;
  backgroundLight: string;
  backgroundDark: string;
  surface: string;
  card: string;
  text: string;
  textSecondary: string;
  // ... 80+ colors
}

Components API

Basic UI

Button

import { Button, FloatButton } from 'feav-ui';

<Button
  title="Submit"
  onPress={() => {}}
  loading={false}
  disabled={false}
  status="primary"        // 'primary' | 'danger' | 'success' | 'warning' | 'info' | 'secondary'
  appearance="filled"     // 'filled' | 'outline' | 'ghost'
  fullWidth={true}
  left={<Icon />}
  right={<Icon />}
  style={{}}
  textStyle={{}}
/>

<FloatButton onPress={() => {}} r={32} style={{}} />

ButtonOutLine

import { ButtonOutLine } from 'feav-ui';

<ButtonOutLine title='Cancel' onPress={() => {}} disabled={false} left={<Icon />} right={<Icon />} style={{}} />;

ButtonText

import { ButtonText } from 'feav-ui';

<ButtonText title='Forgot Password?' onPress={() => {}} loading={false} disabled={false} style={{}} />;

ButtonGradient

import { ButtonGradient } from 'feav-ui';

<ButtonGradient
  title='Login'
  onPress={() => {}}
  loading={false}
  disabled={false}
  colors={['#007AFF', '#00C6FF']} // Gradient colors
  left={<Icon />}
  right={<Icon />}
  style={{}}
/>;

ButtonIcon

import { ButtonIcon } from 'feav-ui';

<ButtonIcon icon={<Icon name='menu' />} onPress={() => {}} size={28} disabled={false} color='#007AFF' style={{}} />;

Text

import { Text } from 'feav-ui';

<Text style={{ fontSize: 16, fontWeight: 'bold' }}>Hello World</Text>;

Title

import { Title } from 'feav-ui';

<Title>Page Title</Title>;

Caption

import { Caption } from 'feav-ui';

<Caption>Subtitle text</Caption>;

TextInput

import { TextInput } from 'feav-ui';

<TextInput
  label='Email'
  placeholder='Enter email'
  value={text}
  onChangeText={(text) => {}}
  error='Invalid email'
  hint="We'll never share your email"
  isPassword={false}
  showPasswordToggle={true}
  leftIcon={<Icon />}
  rightIcon={<Icon />}
  containerStyle={{}}
  keyboardType='default'
  autoCapitalize='none'
  editable={true}
/>;

TextInputMultiline

import { TextInputMultiline } from 'feav-ui';

<TextInputMultiline
  label='Description'
  value={text}
  onChangeText={(text) => {}}
  placeholder='Enter description...'
  editable={true}
  numberOfLines={4}
  style={{}}
/>;

CheckBox

import { CheckBox } from 'feav-ui';

<CheckBox value={checked} onChange={(value) => setChecked(value)} label='Remember me' disabled={false} style={{}} />;

Chip

import { Chip } from 'feav-ui';

<Chip
  label='React'
  value='react'
  onRemove={(value) => removeChip(value)}
  color='#007AFF'
  backgroundColor='#F0F0F0'
  style={{}}
  textStyle={{}}
/>;

Divider

import { Divider } from 'feav-ui';

<Divider color='#E0E0E0' height={1} style={{}} />;

Badge

import { Badge } from 'feav-ui';

<Badge
  label='New'
  color='#007AFF'
  textColor='#FFFFFF'
  size='medium' // 'small' | 'medium' | 'large'
/>;

ActivityIndicator

import { ActivityIndicator } from 'feav-ui';

<ActivityIndicator size='large' color='#007AFF' />;

ProgressBar

import { ProgressBar } from 'feav-ui';

<ProgressBar
  progress={75} // 0-100
  animated={true}
  height={8}
  borderRadius={4}
  backgroundColor='#E0E0E0'
  showProgressText={true}
  color='#007AFF'
/>;

Skeleton

import { Skeleton } from 'feav-ui';

<Skeleton
  definitions={[
    { variant: 'avatar', width: 40, height: 40 },
    { variant: 'text', width: '100%', height: 16 },
    { variant: 'text', width: '60%', height: 16 },
  ]}
  containerStyle={{}}
/>;

// Variants: 'default' | 'text' | 'avatar'

QRCode

import { QRCode } from 'feav-ui';

<QRCode value='https://example.com' size={200} color='#000000' backgroundColor='#FFFFFF' />;

Slider

import { Slider } from 'feav-ui';

<Slider
  value={50}
  onValueChange={(value) => setValue(value)}
  minimumValue={0}
  maximumValue={100}
  step={1}
  disabled={false}
/>;

Circle

import { Circle } from 'feav-ui';

<Circle size={24}>
  <Icon />
</Circle>;

Icon

import { Icon } from 'feav-ui';

<Icon
  name='home'
  size={24}
  color='#007AFF'
  type='MaterialCommunityIcons' // Icon library
  style={{}}
/>;

Dropdown

import { Dropdown } from 'feav-ui';

const data = [
  { key: '1', label: 'Option 1' },
  { key: '2', label: 'Option 2' },
  { key: '3', label: 'Option 3' },
];

<Dropdown
  data={data}
  value={selectedItem}
  onChange={(item) => setSelected(item)}
  placeholder='Select an option'
  searchable={true}
  style={{}}
/>;

TextAreaOutLine

import { TextAreaOutLine } from 'feav-ui';

<TextAreaOutLine
  label='Description'
  value={text}
  onChangeText={(text) => {}}
  placeholder='Enter description...'
  error='Description is required'
  numberOfLines={4}
  style={{}}
/>;

LocationPicker

import { LocationPicker } from 'feav-ui';

<LocationPicker
  label='Location'
  value={location} // { latitude: number, longitude: number, address: string }
  onChange={(location) => {}}
  placeholder='Select location'
  style={{}}
/>;

CollapseSection

import { CollapseSection } from 'feav-ui';

<CollapseSection
  title='Advanced Settings'
  expanded={false}
  onCollapse={(expanded) => {}}
  disabled={false}
  loading={false}>
  <View>
    <Text>Collapsible content here</Text>
  </View>
</CollapseSection>;

ModernChip

import { ModernChip } from 'feav-ui';

<ModernChip
  label='React Native'
  badge={5}
  onPress={() => {}}
  onRemove={(e) => {}}
  color='#007AFF'
  backgroundColor='#F0F0F0'
/>;

ModernLoader

import { ModernLoader } from 'feav-ui';

<ModernLoader progress={0.5} title='Loading' subtitle='Please wait...' size={120} />;

Layout

Card

import { Card } from 'feav-ui';

<Card onPress={() => {}} onLongPress={() => {}} elevated={true} style={{}}>
  <Text>Card Content</Text>
</Card>;

Row

import { Row } from 'feav-ui';

<Row hint='Name' value='John' style={{}} />;

CardFlat

import { CardFlat } from 'feav-ui';

<CardFlat
  title='Card Title'
  description='Card description text'
  image={require('./image.png')}
  onPress={() => {}}
  style={{}}
/>;

CardImage

import { CardImage } from 'feav-ui';

<CardImage source={{ uri: 'https://...' }} resizeMode='cover' onPress={() => {}} width={300} height={200} style={{}} />;

Container

import { Container } from 'feav-ui';

<Container style={{}}>
  <Content />
</Container>;

StatusBar

import { StatusBar } from 'feav-ui';

<StatusBar />;

Timeline

import { Timeline } from 'feav-ui';

<Timeline
  data={[
    { time: new Date(), title: 'Started', description: 'Project started' },
    { time: new Date(), title: 'In Progress', description: 'Working on it' },
  ]}
  orientation='vertical' // 'horizontal' | 'vertical'
/>;

Table, TableRow, TableCell, TableHeader

import { Table, TableRow, TableCell, TableHeader } from 'feav-ui';

<Table>
  <TableRow>
    <TableHeader>Name</TableHeader>
    <TableHeader>Age</TableHeader>
  </TableRow>
  <TableRow>
    <TableCell>John</TableCell>
    <TableCell>25</TableCell>
  </TableRow>
</Table>;

ListItem

import { ListItem } from 'feav-ui';

<ListItem
  title='Settings'
  description='Configure app settings'
  icon={<Icon name='settings' />}
  onPress={() => {}}
  accessoryRight={<Icon name='chevron-right' />}
  disabled={false}
  style={{}}
/>;

RowItem

import { RowItem } from 'feav-ui';

<RowItem style={{}}>
  <Text>Left</Text>
  <Text>Right</Text>
</RowItem>;

AppScrollView

import { AppScrollView } from 'feav-ui';

<AppScrollView showsVerticalScrollIndicator={false} onScroll={() => {}} contentContainerStyle={{}}>
  <Content />
</AppScrollView>;

KeyboardAvoidingView

import { KeyboardAvoidingView } from 'feav-ui';

<KeyboardAvoidingView behavior='padding'>
  <Input />
  <Button />
</KeyboardAvoidingView>;

ModernCard

import { ModernCard } from 'feav-ui';

<ModernCard style={{}}>
  <Text>Card Content</Text>
</ModernCard>;

RowSalary

import { RowSalary } from 'feav-ui';

<RowSalary hint='Total Salary' value='10,000,000' fonttype='roboto-light' />;

Feedback

Modal

import { Modal } from 'feav-ui';

<Modal
  visible={visible}
  onDismiss={() => setVisible(false)}
  dismissable={true}
  transparent={true}
  contentContainerStyle={{}}>
  <Text>Modal Content</Text>
</Modal>;

BottomSheet

import { BottomSheet } from 'feav-ui';

<BottomSheet
  visible={visible}
  onClose={() => setVisible(false)}
  snapPoints={[0.5, 0.8]} // Array of snap points (0-1)
  title='Actions'>
  <Text>Sheet Content</Text>
</BottomSheet>;

Snackbar

import { Snackbar } from 'feav-ui';

<Snackbar
  message='Saved successfully'
  type='success' // 'success' | 'error' | 'warning' | 'info'
  duration={2000}
  visible={visible}
  onDismiss={() => setVisible(false)}
  action={{ label: 'Undo', onPress: () => {} }}
/>;

Message

import { Message } from 'feav-ui';

<Message
  position='left' // 'left' | 'right' | 'center'
  message='Hello!'
  time={new Date()}
  user={{ name: 'John' }}
  onLongPress={() => {}}
/>;

NoData

import { NoData } from 'feav-ui';

<NoData message='No Data Available' />;

Alert

import { Alert } from 'feav-ui';

<Alert
  visible={visible}
  title='Confirm'
  message='Are you sure?'
  buttons={[
    { text: 'Cancel', onPress: () => {}, style: 'cancel' },
    { text: 'OK', onPress: () => {}, style: 'default' },
  ]}
  onDismiss={() => setVisible(false)}
/>;

// Alert helper
import { alert } from 'feav-ui';

alert({
  title: 'Hello',
  message: 'World',
  buttons: [{ text: 'OK' }],
});

ErrorBoundary

import { ErrorBoundary } from 'feav-ui';

<ErrorBoundary fallback={<ErrorFallback />} onError={(error, errorInfo) => {}}>
  <App />
</ErrorBoundary>;

ModalView

import { ModalView } from 'feav-ui';

<ModalView visible={visible} onDismiss={() => setVisible(false)}>
  <View>
    <Text>Bottom Sheet Content</Text>
  </View>
</ModalView>;

Media

Image

import { Image } from 'feav-ui';

<Image source={{ uri: 'https://...' }} style={{ width: 200, height: 200 }} placeholder={true} />;

ImageWithLoading

import { ImageWithLoading } from 'feav-ui';

<ImageWithLoading source={{ uri: 'https://...' }} style={{ width: 200, height: 200 }} placeholderHeight={180} />;

Avatar

import { Avatar } from 'feav-ui';

<Avatar source={{ uri: 'https://...' }} size={50} onPress={() => {}} placeholder={<View />} />;

AvatarImage

import { AvatarImage } from 'feav-ui';

<AvatarImage source={{ uri: 'https://...' }} size={64} style={{}} />;

AvatarChildren

import { AvatarChildren } from 'feav-ui';

<AvatarChildren size={50}>
  <Icon name='person' />
</AvatarChildren>;

AvatarImages

import { AvatarImages } from 'feav-ui';

<AvatarImages sources={[{ uri: 'https://...' }, { uri: 'https://...' }]} size={64} />;

AvatarText

import { AvatarText } from 'feav-ui';

<AvatarText size={48} color='#007AFF'>
  JD
</AvatarText>;

Carousel

import { Carousel } from 'feav-ui';

<Carousel
  data={[
    { key: '1', image: 'https://...', title: 'Slide 1' },
    { key: '2', image: 'https://...', title: 'Slide 2' },
  ]}
  autoPlay={true}
  duration={5000}
  showPagination={true}
  onItemPress={(item) => {}}
  height={200}
/>;

CarouselChildren

import { CarouselChildren } from 'feav-ui';

<CarouselChildren
  data={[
    {
      key: '1',
      children: (
        <View>
          <Text>Slide 1</Text>
        </View>
      ),
    },
    {
      key: '2',
      children: (
        <View>
          <Text>Slide 2</Text>
        </View>
      ),
    },
  ]}
  autoPlay={true}
  showPagination={true}
  height={200}
/>;

VideoPlayer

import { VideoPlayer } from 'feav-ui';

<VideoPlayer source={{ uri: 'https://...' }} autoPlay={false} muted={false} loop={false} />;

Navigation

BottomTab

import { BottomTab } from 'feav-ui';

<BottomTab
  tabs={[
    { name: 'Home', label: 'Home', icon: <HomeIcon />, badge: 2 },
    { name: 'Profile', label: 'Profile', icon: <ProfileIcon /> },
    { name: 'Settings', label: 'Settings', icon: <SettingsIcon /> },
  ]}
  activeTab={activeTab}
  onTabPress={(tab) => setActiveTab(tab.name)}
  showLabels={true}
/>;

TabIcon

import { TabIcon } from 'feav-ui';

<TabIcon name='home' size={24} color='#007AFF' focused={false} badge={0} />;

ThemeSwitch

import { ThemeSwitch } from 'feav-ui';

<ThemeSwitch
  onToggle={(theme) => console.log('Theme:', theme)}
  size='medium' // 'small' | 'medium' | 'large'
  style={{}}
/>;

Charts

PieChart

import { PieChart } from 'feav-ui';

<PieChart
  data={[
    { id: '1', value: 30, label: 'Apple', color: '#FF0000' },
    { id: '2', value: 20, label: 'Banana', color: '#FFFF00' },
    { id: '3', value: 50, label: 'Orange', color: '#FFA500' },
  ]}
  width={200}
  height={200}
  showLegend={true}
  showValues={true}
/>;

LineChart

import { LineChart } from 'feav-ui';

<LineChart
  data={{
    labels: ['Jan', 'Feb', 'Mar', 'Apr'],
    datasets: [
      { data: [10, 25, 15, 30], color: '#007AFF' },
      { data: [5, 15, 25, 20], color: '#34C759' },
    ],
  }}
  width={300}
  height={200}
  showGrid={true}
  showLabels={true}
  showValues={true}
/>;

BarChart

import { BarChart } from 'feav-ui';

<BarChart
  data={{
    labels: ['Jan', 'Feb', 'Mar', 'Apr'],
    datasets: [{ data: [10, 25, 15, 30], color: '#007AFF' }],
  }}
  width={300}
  height={200}
  showGrid={true}
  showLabels={true}
  showValues={true}
/>;

Advanced

WebView

import { WebView } from 'feav-ui';

<WebView source={{ uri: 'https://example.com' }} style={{ flex: 1 }} />;

SplashScreen

import { SplashScreen } from 'feav-ui';

<SplashScreen imageSource={require('./assets/logo.png')} duration={2000} onFinish={() => navigate('Home')} />;

Portal

import { Portal, usePortal } from 'feav-ui';

function MyComponent() {
  const { mount, unmount } = usePortal();

  useEffect(() => {
    mount('modal', <ModalContent />);
    return () => unmount('modal');
  }, []);

  return <Content />;
}

<Portal>
  <App />
</Portal>;

ColorPicker

import { ColorPicker } from 'feav-ui';

<ColorPicker initialColor='#007AFF' onColorChange={(color) => setColor(color)} />;

Calendar & DatePicker

Calendar

import { Calendar } from 'feav-ui';

<Calendar
  language='en' // 'en' | 'vi' | 'ja' | 'ko' | 'zh' | 'es'
  date={new Date()} // Selected date
  min={new Date(2020, 0, 1)} // Minimum date
  max={new Date(2030, 11, 31)} // Maximum date
  onSelect={(date) => console.log(date)}
  onVisibleDateChange={(date) => console.log(date)}
  style={{}}
  boundingMonth={false} // If true, disable days outside current month
  renderDay={(info, style) => <CustomDayCell />}
  renderHeader={() => <CustomHeader />}
  renderFooter={() => <CustomFooter />}
/>;

Calendar Props

| Prop | Type | Default | Description | | --------------------- | ---------------------------------------------------- | ------------ | ----------------------------------------- | | language | 'en' \| 'vi' \| 'ja' \| 'ko' \| 'zh' \| 'es' | 'en' | Calendar language | | date | Date | new Date() | Currently selected date | | min | Date | - | Minimum selectable date | | max | Date | - | Maximum selectable date | | onSelect | (date: Date) => void | - | Callback when date is selected | | onVisibleDateChange | (date: Date) => void | - | Callback when visible month changes | | boundingMonth | boolean | false | Enable/disable days outside current month | | renderDay | (info: CalendarDayCellInfo, style) => ReactElement | - | Custom day cell renderer | | renderHeader | () => ReactElement | - | Custom header renderer | | renderFooter | () => ReactElement | - | Custom footer renderer |

CalendarDayCellInfo Type

interface CalendarDayCellInfo {
  date: Date;
  bounding: boolean; // true if day is in current month
}

DateTimePicker

import { DateTimePicker } from 'feav-ui';

const [visible, setVisible] = useState(false);
const [selectedDate, setSelectedDate] = useState(new Date());

<DateTimePicker
  type='datetime' // 'date' | 'time' | 'datetime'
  isVisible={visible}
  date={selectedDate}
  onChange={(date) => {
    setSelectedDate(date);
    setVisible(false);
  }}
  onHide={() => setVisible(false)}
  minDate={new Date(2020, 0, 1)}
  maxDate={new Date(2030, 11, 31)}
/>;

DateTimePicker Props

| Prop | Type | Default | Description | | ----------- | -------------------------------- | ------------ | -------------------------------- | | type | 'date' \| 'time' \| 'datetime' | 'datetime' | Picker mode | | isVisible | boolean | - | Controls modal visibility | | date | Date | new Date() | Initial date/time | | onChange | (date: Date) => void | - | Callback when date is confirmed | | onHide | () => void | - | Callback when modal is dismissed | | minDate | Date | - | Minimum selectable date | | maxDate | Date | - | Maximum selectable date |


Theme Colors Reference

Primary Colors

| Key | Description | | -------------- | ------------------- | | primary | Primary brand color | | primaryDark | Darker primary | | primaryLight | Lighter primary | | secondary | Secondary color | | accent | Accent color |

Background Colors

| Key | Description | | ----------------- | ----------------- | | background | Main background | | backgroundLight | Light background | | backgroundDark | Dark background | | surface | Card/surface | | card | Card background | | layout | Layout background |

Text Colors

| Key | Description | | --------------- | -------------- | | text | Primary text | | textSecondary | Secondary text | | textLight | Light text | | textDark | Dark text | | textHint | Hint text | | textDisabled | Disabled text | | textInverse | Inverse text |

State Colors

| Key | Description | | --------- | ------------- | | success | Success state | | warning | Warning state | | error | Error state | | info | Info state | | danger | Danger state |

Border Colors

| Key | Description | | ------------- | -------------- | | border | Default border | | borderLight | Light border | | borderDark | Dark border | | divider | Divider line |


Release

# Build and publish
.\publish.ps1

# Or manual
npm run build
npm version patch
npm publish --access public

License

MIT