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.
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-uiPeer Dependencies
npm install react react-native zustand @react-native-community/sliderQuick 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 storageTypes
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 publicLicense
MIT
