react-native-yastools 1.0.1 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +55 -140
- package/dist/assets/imgs/close.png +0 -0
- package/dist/components/BottomTabs/__tests__/BottomTabs.test.js +69 -0
- package/dist/components/BottomTabs/index.d.ts +5 -0
- package/dist/components/BottomTabs/index.js +82 -0
- package/dist/components/BottomTabs/styles.d.ts +43 -0
- package/dist/components/BottomTabs/styles.js +44 -0
- package/dist/components/BottomTabs/type.d.ts +53 -0
- package/dist/components/BottomTabs/type.js +1 -0
- package/dist/components/Button/__tests__/Button.test.d.ts +1 -0
- package/dist/components/Button/__tests__/{YasButton.test.js → Button.test.js} +9 -9
- package/dist/components/Button/index.d.ts +3 -3
- package/dist/components/Button/index.js +30 -20
- package/dist/components/Button/styles.d.ts +1 -1
- package/dist/components/Button/styles.js +1 -1
- package/dist/components/Button/type.d.ts +7 -3
- package/dist/components/ConfirmationPopUp/__tests__/ConfirmationPopUp.test.d.ts +1 -0
- package/dist/components/ConfirmationPopUp/__tests__/ConfirmationPopUp.test.js +62 -0
- package/dist/components/ConfirmationPopUp/index.d.ts +15 -0
- package/dist/components/ConfirmationPopUp/index.js +41 -0
- package/dist/components/ConfirmationPopUp/styles.d.ts +79 -0
- package/dist/components/ConfirmationPopUp/styles.js +81 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.js +5 -3
- package/dist/interactions.d.ts +8 -0
- package/dist/interactions.js +47 -0
- package/dist/theme/index.d.ts +3 -1
- package/dist/theme/index.js +3 -1
- package/package.json +6 -4
- package/src/assets/imgs/close.png +0 -0
- package/src/components/BottomTabs/__tests__/BottomTabs.test.tsx +142 -0
- package/src/components/BottomTabs/index.tsx +134 -0
- package/src/components/BottomTabs/styles.ts +45 -0
- package/src/components/BottomTabs/type.ts +61 -0
- package/src/components/Button/__tests__/{YasButton.test.tsx → Button.test.tsx} +9 -9
- package/src/components/Button/index.tsx +39 -21
- package/src/components/Button/styles.ts +1 -1
- package/src/components/Button/type.ts +7 -3
- package/src/components/ConfirmationPopUp/__tests__/ConfirmationPopUp.test.tsx +125 -0
- package/src/components/ConfirmationPopUp/index.tsx +103 -0
- package/src/components/ConfirmationPopUp/styles.ts +83 -0
- package/src/declarations.d.ts +5 -0
- package/src/index.ts +11 -4
- package/src/interactions.ts +58 -0
- package/src/theme/index.ts +3 -1
- /package/dist/components/{Button/__tests__/YasButton.test.d.ts → BottomTabs/__tests__/BottomTabs.test.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
#
|
|
1
|
+
# 🚀 React Native Yastools
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
[](https://www.npmjs.com/package/react-native-yastools)
|
|
3
|
+
A collection of **premium, high-performance** UI components and utilities for React Native. Built for speed, aesthetics, and developer experience.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
## ✨ Features
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
* **🎨 Themable:** Fully customizable design system.
|
|
8
|
+
* **⚡ Performant:** Optimized for high frame rates and smooth interactions.
|
|
9
|
+
* **📱 Native Feel:** Haptic feedback and native animations baked in.
|
|
10
|
+
* **🔌 Plug & Play:** Zero-config components ready to drop into your app.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## 📦 Installation
|
|
9
15
|
|
|
10
16
|
```bash
|
|
11
17
|
npm install react-native-yastools
|
|
@@ -13,161 +19,70 @@ npm install react-native-yastools
|
|
|
13
19
|
yarn add react-native-yastools
|
|
14
20
|
```
|
|
15
21
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
This package requires the following peer dependencies to be installed in your project:
|
|
19
|
-
|
|
20
|
-
- `react` >= 17.0.0
|
|
21
|
-
- `react-native` >= 0.64.0
|
|
22
|
-
|
|
23
|
-
## Components
|
|
22
|
+
> **Note:** Ensure you have `react-native-reanimated` installed if you plan to use advanced animations.
|
|
24
23
|
|
|
25
|
-
|
|
24
|
+
---
|
|
26
25
|
|
|
27
|
-
|
|
26
|
+
## 🛠️ Components
|
|
28
27
|
|
|
29
|
-
|
|
28
|
+
### 🟢 Button
|
|
29
|
+
A highly versatile button with built-in loading states, scaling animations, and debounce protection.
|
|
30
30
|
|
|
31
31
|
```tsx
|
|
32
|
-
import {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
32
|
+
import { Button } from 'react-native-yastools';
|
|
33
|
+
|
|
34
|
+
<Button
|
|
35
|
+
text="Confirm Order"
|
|
36
|
+
onPress={handleCheckout}
|
|
37
|
+
primaryColor="#007AFF"
|
|
38
|
+
animateScale={0.95} // 🪄 Smooth press animation
|
|
39
|
+
debounceTime={500} // 🛡️ Prevents double-taps
|
|
40
|
+
fetching={isLoading}
|
|
41
|
+
/>
|
|
42
42
|
```
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
### 💬 ConfirmationPopUp (Modal)
|
|
45
|
+
A beautiful, promise-based confirmation dialog.
|
|
45
46
|
|
|
46
47
|
```tsx
|
|
47
|
-
import {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
<YasButton
|
|
60
|
-
text="Submit"
|
|
61
|
-
onPress={handlePress}
|
|
62
|
-
fetching={loading}
|
|
63
|
-
loaderColor="#FFFFFF"
|
|
64
|
-
/>
|
|
65
|
-
);
|
|
66
|
-
};
|
|
48
|
+
import { ConfirmationPopUp } from 'react-native-yastools';
|
|
49
|
+
|
|
50
|
+
<ConfirmationPopUp
|
|
51
|
+
visible={isVisible}
|
|
52
|
+
title="Delete Account?"
|
|
53
|
+
message="This action cannot be undone."
|
|
54
|
+
onConfirm={deleteUser}
|
|
55
|
+
onClose={() => setVisible(false)}
|
|
56
|
+
confirmText="Delete"
|
|
57
|
+
cancelText="Keep"
|
|
58
|
+
loading={isDeleting}
|
|
59
|
+
/>
|
|
67
60
|
```
|
|
68
61
|
|
|
69
|
-
|
|
62
|
+
---
|
|
70
63
|
|
|
71
|
-
|
|
72
|
-
import { YasButton } from 'react-native-yastools';
|
|
73
|
-
|
|
74
|
-
const MyComponent = () => {
|
|
75
|
-
return (
|
|
76
|
-
<YasButton
|
|
77
|
-
text="Custom Button"
|
|
78
|
-
onPress={() => {}}
|
|
79
|
-
primaryColor="#FF6B6B"
|
|
80
|
-
disabledColor="#CCCCCC"
|
|
81
|
-
additionalStyle={{
|
|
82
|
-
width: 200,
|
|
83
|
-
height: 60,
|
|
84
|
-
borderRadius: 30,
|
|
85
|
-
}}
|
|
86
|
-
textStyle={{
|
|
87
|
-
fontSize: 18,
|
|
88
|
-
fontWeight: 'bold',
|
|
89
|
-
}}
|
|
90
|
-
/>
|
|
91
|
-
);
|
|
92
|
-
};
|
|
93
|
-
```
|
|
64
|
+
## 🎨 Theme & Utils
|
|
94
65
|
|
|
95
|
-
|
|
66
|
+
The library exposes its core theme and interaction helpers:
|
|
96
67
|
|
|
97
68
|
```tsx
|
|
98
|
-
import {
|
|
99
|
-
|
|
100
|
-
const MyComponent = () => {
|
|
101
|
-
return (
|
|
102
|
-
<YasButton
|
|
103
|
-
text="Settings"
|
|
104
|
-
icon={require('./assets/settings-icon.png')}
|
|
105
|
-
onPress={() => navigation.navigate('Settings')}
|
|
106
|
-
/>
|
|
107
|
-
);
|
|
108
|
-
};
|
|
109
|
-
```
|
|
69
|
+
import { COLORS, preventMultiPress } from 'react-native-yastools';
|
|
110
70
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
| Prop | Type | Default | Description |
|
|
114
|
-
|------|------|---------|-------------|
|
|
115
|
-
| `onPress` | `() => void` | **Required** | Callback function when button is pressed |
|
|
116
|
-
| `text` | `string` | `undefined` | Text to display on the button |
|
|
117
|
-
| `icon` | `ImageSourcePropType` | `undefined` | Icon to display on the button |
|
|
118
|
-
| `disabled` | `boolean` | `false` | Whether the button is disabled |
|
|
119
|
-
| `fetching` | `boolean` | `false` | Whether to show loading indicator |
|
|
120
|
-
| `additionalStyle` | `StyleProp<ViewStyle>` | `undefined` | Additional styles for the button container |
|
|
121
|
-
| `textStyle` | `StyleProp<TextStyle>` | `undefined` | Custom styles for the button text |
|
|
122
|
-
| `iconStyle` | `StyleProp<ImageStyle>` | `undefined` | Custom styles for the icon |
|
|
123
|
-
| `loaderColor` | `string` | Theme primary | Color of the loading indicator |
|
|
124
|
-
| `primaryColor` | `string` | `#007AFF` | Custom primary/background color |
|
|
125
|
-
| `disabledColor` | `string` | `#E5E5E5` | Custom disabled background color |
|
|
126
|
-
| `debounceTime` | `number` | `1000` | Debounce time in ms to prevent multiple clicks |
|
|
127
|
-
| `activeOpacity` | `number` | `0.8` | Opacity when pressing the button |
|
|
128
|
-
| `testID` | `string` | `undefined` | Test ID for testing purposes |
|
|
129
|
-
|
|
130
|
-
## Theme Customization
|
|
131
|
-
|
|
132
|
-
You can import the default theme values to use in your own components:
|
|
71
|
+
// Use standard colors
|
|
72
|
+
const myStyle = { color: COLORS.primary };
|
|
133
73
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const styles = StyleSheet.create({
|
|
138
|
-
container: {
|
|
139
|
-
backgroundColor: COLORS.primary,
|
|
140
|
-
},
|
|
141
|
-
text: {
|
|
142
|
-
fontSize: FONT_SIZES.f16,
|
|
143
|
-
},
|
|
144
|
-
});
|
|
74
|
+
// Wrap your own handlers
|
|
75
|
+
const safeHandler = preventMultiPress(() => console.log('Safe!'), 1000);
|
|
145
76
|
```
|
|
146
77
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
### Running Tests
|
|
150
|
-
|
|
151
|
-
```bash
|
|
152
|
-
npm test
|
|
153
|
-
|
|
154
|
-
# Watch mode
|
|
155
|
-
npm run test:watch
|
|
156
|
-
|
|
157
|
-
# Coverage report
|
|
158
|
-
npm run test:coverage
|
|
159
|
-
```
|
|
160
|
-
|
|
161
|
-
### Building
|
|
162
|
-
|
|
163
|
-
```bash
|
|
164
|
-
npm run build
|
|
165
|
-
```
|
|
78
|
+
---
|
|
166
79
|
|
|
167
|
-
## Contributing
|
|
80
|
+
## 🤝 Contributing
|
|
168
81
|
|
|
169
|
-
|
|
82
|
+
1. Clone the repo
|
|
83
|
+
2. Run `npm install`
|
|
84
|
+
3. Test changes with `npm run local-publish`
|
|
170
85
|
|
|
171
|
-
|
|
86
|
+
---
|
|
172
87
|
|
|
173
|
-
|
|
88
|
+
Built with ❤️ by **Yassine Ben Zriouil**.
|
|
Binary file
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { render, fireEvent } from '@testing-library/react-native';
|
|
3
|
+
import BottomTabs from '../index';
|
|
4
|
+
// Mock navigation
|
|
5
|
+
const mockNavigate = jest.fn();
|
|
6
|
+
const mockNavigation = {
|
|
7
|
+
navigate: mockNavigate,
|
|
8
|
+
};
|
|
9
|
+
// Sample tabs for testing
|
|
10
|
+
const sampleTabs = [
|
|
11
|
+
{ name: 'Home', route: 'Home', icon: { uri: 'https://example.com/home.png' } },
|
|
12
|
+
{ name: 'Search', route: 'Search', icon: { uri: 'https://example.com/search.png' } },
|
|
13
|
+
{ name: 'Profile', route: 'Profile', icon: { uri: 'https://example.com/profile.png' } },
|
|
14
|
+
];
|
|
15
|
+
describe('BottomTabs', () => {
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
jest.clearAllMocks();
|
|
18
|
+
});
|
|
19
|
+
it('renders all tabs correctly', () => {
|
|
20
|
+
const { getByText } = render(<BottomTabs navigation={mockNavigation} currentRoute="Home" tabs={sampleTabs}/>);
|
|
21
|
+
expect(getByText('Home')).toBeTruthy();
|
|
22
|
+
expect(getByText('Search')).toBeTruthy();
|
|
23
|
+
expect(getByText('Profile')).toBeTruthy();
|
|
24
|
+
});
|
|
25
|
+
it('navigates to correct route when tab is pressed', () => {
|
|
26
|
+
const { getByText } = render(<BottomTabs navigation={mockNavigation} currentRoute="Home" tabs={sampleTabs}/>);
|
|
27
|
+
fireEvent.press(getByText('Search'));
|
|
28
|
+
expect(mockNavigate).toHaveBeenCalledWith('Search');
|
|
29
|
+
});
|
|
30
|
+
it('calls onTabPress callback when provided', () => {
|
|
31
|
+
const onTabPressMock = jest.fn();
|
|
32
|
+
const { getByText } = render(<BottomTabs navigation={mockNavigation} currentRoute="Home" tabs={sampleTabs} onTabPress={onTabPressMock}/>);
|
|
33
|
+
fireEvent.press(getByText('Profile'));
|
|
34
|
+
expect(onTabPressMock).toHaveBeenCalledWith('Profile');
|
|
35
|
+
expect(mockNavigate).toHaveBeenCalledWith('Profile');
|
|
36
|
+
});
|
|
37
|
+
it('does not navigate when tab is disabled', () => {
|
|
38
|
+
const disabledTabs = [
|
|
39
|
+
{ name: 'Home', route: 'Home', icon: { uri: 'https://example.com/home.png' } },
|
|
40
|
+
{ name: 'Search', route: 'Search', icon: { uri: 'https://example.com/search.png' }, disabled: true },
|
|
41
|
+
];
|
|
42
|
+
const { getByText } = render(<BottomTabs navigation={mockNavigation} currentRoute="Home" tabs={disabledTabs}/>);
|
|
43
|
+
fireEvent.press(getByText('Search'));
|
|
44
|
+
expect(mockNavigate).not.toHaveBeenCalled();
|
|
45
|
+
});
|
|
46
|
+
it('applies custom testID to tabs', () => {
|
|
47
|
+
const tabsWithTestId = [
|
|
48
|
+
{ name: 'Home', route: 'Home', icon: { uri: 'https://example.com/home.png' }, testID: 'home-tab' },
|
|
49
|
+
];
|
|
50
|
+
const { getByTestId } = render(<BottomTabs navigation={mockNavigation} currentRoute="Home" tabs={tabsWithTestId}/>);
|
|
51
|
+
expect(getByTestId('home-tab')).toBeTruthy();
|
|
52
|
+
});
|
|
53
|
+
it('renders with container testID', () => {
|
|
54
|
+
const { getByTestId } = render(<BottomTabs navigation={mockNavigation} currentRoute="Home" tabs={sampleTabs} testID="bottom-tabs"/>);
|
|
55
|
+
expect(getByTestId('bottom-tabs')).toBeTruthy();
|
|
56
|
+
});
|
|
57
|
+
it('renders empty when no tabs are provided', () => {
|
|
58
|
+
const { queryByText } = render(<BottomTabs navigation={mockNavigation} currentRoute="Home" tabs={[]}/>);
|
|
59
|
+
expect(queryByText('Home')).toBeNull();
|
|
60
|
+
});
|
|
61
|
+
it('handles single tab correctly', () => {
|
|
62
|
+
const singleTab = [
|
|
63
|
+
{ name: 'Home', route: 'Home', icon: { uri: 'https://example.com/home.png' } },
|
|
64
|
+
];
|
|
65
|
+
const { getByText, queryByText } = render(<BottomTabs navigation={mockNavigation} currentRoute="Home" tabs={singleTab}/>);
|
|
66
|
+
expect(getByText('Home')).toBeTruthy();
|
|
67
|
+
expect(queryByText('Search')).toBeNull();
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import React, { memo, useCallback } from 'react';
|
|
2
|
+
import { TouchableOpacity, Image, View, Text } from 'react-native';
|
|
3
|
+
import styles from './styles';
|
|
4
|
+
import { COLORS } from '../../theme';
|
|
5
|
+
/**
|
|
6
|
+
* BottomTabs - A customizable bottom navigation component for React Native
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* import { BottomTabs } from 'react-native-yastools';
|
|
11
|
+
*
|
|
12
|
+
* const tabs = [
|
|
13
|
+
* { name: 'Home', route: 'Home', icon: require('./icons/home.png') },
|
|
14
|
+
* { name: 'Search', route: 'Search', icon: require('./icons/search.png') },
|
|
15
|
+
* { name: 'Profile', route: 'Profile', icon: require('./icons/profile.png') },
|
|
16
|
+
* ];
|
|
17
|
+
*
|
|
18
|
+
* <BottomTabs
|
|
19
|
+
* navigation={navigation}
|
|
20
|
+
* currentRoute="Home"
|
|
21
|
+
* tabs={tabs}
|
|
22
|
+
* activeColor="#007AFF"
|
|
23
|
+
* />
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
const BottomTabs = ({ navigation, currentRoute, tabs, containerStyle, tabItemStyle, activeTabItemStyle, textStyle, activeTextStyle, iconStyle, activeIconStyle, activeColor, inactiveColor, onTabPress, activeOpacity = 0.8, testID, }) => {
|
|
27
|
+
const handleTabPress = useCallback((tab) => {
|
|
28
|
+
if (tab.disabled)
|
|
29
|
+
return;
|
|
30
|
+
// Call custom callback if provided
|
|
31
|
+
if (onTabPress) {
|
|
32
|
+
onTabPress(tab.route);
|
|
33
|
+
}
|
|
34
|
+
// Navigate to the route
|
|
35
|
+
navigation.navigate(tab.route);
|
|
36
|
+
}, [navigation, onTabPress]);
|
|
37
|
+
const renderTab = useCallback((tab, index) => {
|
|
38
|
+
const isActive = currentRoute === tab.route;
|
|
39
|
+
// Determine icon tint color
|
|
40
|
+
const iconTintColor = isActive
|
|
41
|
+
? activeColor || COLORS.primary
|
|
42
|
+
: inactiveColor || COLORS.black;
|
|
43
|
+
// Determine text color
|
|
44
|
+
const textColor = isActive
|
|
45
|
+
? activeColor || COLORS.primary
|
|
46
|
+
: inactiveColor || COLORS.black;
|
|
47
|
+
// Determine which icon to use
|
|
48
|
+
const iconSource = isActive && tab.activeIcon ? tab.activeIcon : tab.icon;
|
|
49
|
+
// Merge styles based on active state
|
|
50
|
+
const tabStyle = isActive
|
|
51
|
+
? [styles.navItemActive, tabItemStyle, activeTabItemStyle]
|
|
52
|
+
: [styles.navItem, tabItemStyle];
|
|
53
|
+
const tabTextStyle = isActive
|
|
54
|
+
? [styles.navTextActive, { color: textColor }, textStyle, activeTextStyle]
|
|
55
|
+
: [styles.navText, { color: textColor }, textStyle];
|
|
56
|
+
const tabIconStyle = isActive
|
|
57
|
+
? [styles.navIconActive, { tintColor: iconTintColor }, iconStyle, activeIconStyle]
|
|
58
|
+
: [styles.navIcon, { tintColor: iconTintColor }, iconStyle];
|
|
59
|
+
return (<TouchableOpacity key={`tab-${tab.route}-${index}`} style={tabStyle} activeOpacity={activeOpacity} onPress={() => handleTabPress(tab)} disabled={tab.disabled} testID={tab.testID || `tab-${tab.route}`}>
|
|
60
|
+
<Image resizeMode="contain" source={iconSource} style={tabIconStyle}/>
|
|
61
|
+
<Text style={tabTextStyle} numberOfLines={1}>
|
|
62
|
+
{tab.name}
|
|
63
|
+
</Text>
|
|
64
|
+
</TouchableOpacity>);
|
|
65
|
+
}, [
|
|
66
|
+
currentRoute,
|
|
67
|
+
activeColor,
|
|
68
|
+
inactiveColor,
|
|
69
|
+
tabItemStyle,
|
|
70
|
+
activeTabItemStyle,
|
|
71
|
+
textStyle,
|
|
72
|
+
activeTextStyle,
|
|
73
|
+
iconStyle,
|
|
74
|
+
activeIconStyle,
|
|
75
|
+
activeOpacity,
|
|
76
|
+
handleTabPress,
|
|
77
|
+
]);
|
|
78
|
+
return (<View style={[styles.BottomTabsContainer, containerStyle]} testID={testID}>
|
|
79
|
+
{tabs.map(renderTab)}
|
|
80
|
+
</View>);
|
|
81
|
+
};
|
|
82
|
+
export default memo(BottomTabs);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default styles for the BottomTabs component
|
|
3
|
+
* Uses standard React Native StyleSheet for maximum compatibility
|
|
4
|
+
*/
|
|
5
|
+
declare const _default: {
|
|
6
|
+
BottomTabsContainer: {
|
|
7
|
+
width: "100%";
|
|
8
|
+
flexDirection: "row";
|
|
9
|
+
backgroundColor: string;
|
|
10
|
+
paddingBottom: number;
|
|
11
|
+
};
|
|
12
|
+
navItem: {
|
|
13
|
+
flex: number;
|
|
14
|
+
justifyContent: "flex-end";
|
|
15
|
+
alignItems: "center";
|
|
16
|
+
paddingVertical: number;
|
|
17
|
+
};
|
|
18
|
+
navItemActive: {
|
|
19
|
+
flex: number;
|
|
20
|
+
justifyContent: "flex-end";
|
|
21
|
+
alignItems: "center";
|
|
22
|
+
paddingVertical: number;
|
|
23
|
+
};
|
|
24
|
+
navText: {
|
|
25
|
+
fontFamily: string;
|
|
26
|
+
fontSize: number;
|
|
27
|
+
marginTop: number;
|
|
28
|
+
};
|
|
29
|
+
navTextActive: {
|
|
30
|
+
fontFamily: string;
|
|
31
|
+
fontSize: number;
|
|
32
|
+
marginTop: number;
|
|
33
|
+
};
|
|
34
|
+
navIcon: {
|
|
35
|
+
width: number;
|
|
36
|
+
height: number;
|
|
37
|
+
};
|
|
38
|
+
navIconActive: {
|
|
39
|
+
width: number;
|
|
40
|
+
height: number;
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
export default _default;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { StyleSheet, Platform } from 'react-native';
|
|
2
|
+
import COLORS, { FONT_SIZES, FONT_FAMILY } from '../../theme';
|
|
3
|
+
/**
|
|
4
|
+
* Default styles for the BottomTabs component
|
|
5
|
+
* Uses standard React Native StyleSheet for maximum compatibility
|
|
6
|
+
*/
|
|
7
|
+
export default StyleSheet.create({
|
|
8
|
+
BottomTabsContainer: {
|
|
9
|
+
width: '100%',
|
|
10
|
+
flexDirection: 'row',
|
|
11
|
+
backgroundColor: COLORS.white,
|
|
12
|
+
paddingBottom: Platform.OS === 'ios' ? 10 : 0,
|
|
13
|
+
},
|
|
14
|
+
navItem: {
|
|
15
|
+
flex: 1,
|
|
16
|
+
justifyContent: 'flex-end',
|
|
17
|
+
alignItems: 'center',
|
|
18
|
+
paddingVertical: 8,
|
|
19
|
+
},
|
|
20
|
+
navItemActive: {
|
|
21
|
+
flex: 1,
|
|
22
|
+
justifyContent: 'flex-end',
|
|
23
|
+
alignItems: 'center',
|
|
24
|
+
paddingVertical: 8,
|
|
25
|
+
},
|
|
26
|
+
navText: {
|
|
27
|
+
fontFamily: FONT_FAMILY.InterRegular,
|
|
28
|
+
fontSize: FONT_SIZES.f11,
|
|
29
|
+
marginTop: 5,
|
|
30
|
+
},
|
|
31
|
+
navTextActive: {
|
|
32
|
+
fontFamily: FONT_FAMILY.InterRegular,
|
|
33
|
+
fontSize: FONT_SIZES.f11,
|
|
34
|
+
marginTop: 5,
|
|
35
|
+
},
|
|
36
|
+
navIcon: {
|
|
37
|
+
width: 20,
|
|
38
|
+
height: 20,
|
|
39
|
+
},
|
|
40
|
+
navIconActive: {
|
|
41
|
+
width: 20,
|
|
42
|
+
height: 20,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { StyleProp, ViewStyle, TextStyle, ImageSourcePropType, ImageStyle } from 'react-native';
|
|
2
|
+
/**
|
|
3
|
+
* Represents a single tab item in the BottomTabs component
|
|
4
|
+
*/
|
|
5
|
+
export interface TabItem {
|
|
6
|
+
/** Display name shown below the icon */
|
|
7
|
+
name: string;
|
|
8
|
+
/** Route name to navigate to when tab is pressed */
|
|
9
|
+
route: string;
|
|
10
|
+
/** Icon to display for the tab (required) */
|
|
11
|
+
icon: ImageSourcePropType;
|
|
12
|
+
/** Optional: Active state icon (if different from default) */
|
|
13
|
+
activeIcon?: ImageSourcePropType;
|
|
14
|
+
/** Optional: Whether this tab is disabled */
|
|
15
|
+
disabled?: boolean;
|
|
16
|
+
/** Optional: Test ID for testing purposes */
|
|
17
|
+
testID?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Props for the BottomTabs component
|
|
21
|
+
*/
|
|
22
|
+
export interface BottomTabsProps {
|
|
23
|
+
/** Navigation object from React Navigation */
|
|
24
|
+
navigation: any;
|
|
25
|
+
/** Currently active route name */
|
|
26
|
+
currentRoute: string;
|
|
27
|
+
/** Array of tab items to render */
|
|
28
|
+
tabs: TabItem[];
|
|
29
|
+
/** Optional: Custom styles for the container */
|
|
30
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
31
|
+
/** Optional: Custom styles for tab items */
|
|
32
|
+
tabItemStyle?: StyleProp<ViewStyle>;
|
|
33
|
+
/** Optional: Custom styles for active tab items */
|
|
34
|
+
activeTabItemStyle?: StyleProp<ViewStyle>;
|
|
35
|
+
/** Optional: Custom styles for tab text */
|
|
36
|
+
textStyle?: StyleProp<TextStyle>;
|
|
37
|
+
/** Optional: Custom styles for active tab text */
|
|
38
|
+
activeTextStyle?: StyleProp<TextStyle>;
|
|
39
|
+
/** Optional: Custom styles for tab icons */
|
|
40
|
+
iconStyle?: StyleProp<ImageStyle>;
|
|
41
|
+
/** Optional: Custom styles for active tab icons */
|
|
42
|
+
activeIconStyle?: StyleProp<ImageStyle>;
|
|
43
|
+
/** Optional: Active color for icons and text (overrides theme) */
|
|
44
|
+
activeColor?: string;
|
|
45
|
+
/** Optional: Inactive color for icons and text (overrides theme) */
|
|
46
|
+
inactiveColor?: string;
|
|
47
|
+
/** Optional: Callback when a tab is pressed (receives route name) */
|
|
48
|
+
onTabPress?: (route: string) => void;
|
|
49
|
+
/** Optional: Active opacity when pressing tabs (default: 0.8) */
|
|
50
|
+
activeOpacity?: number;
|
|
51
|
+
/** Test ID for testing purposes */
|
|
52
|
+
testID?: string;
|
|
53
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { render, fireEvent } from '@testing-library/react-native';
|
|
3
|
-
import
|
|
4
|
-
describe('
|
|
3
|
+
import Button from '../index';
|
|
4
|
+
describe('Button', () => {
|
|
5
5
|
it('renders correctly with text', () => {
|
|
6
|
-
const { getByText } = render(<
|
|
6
|
+
const { getByText } = render(<Button text="Press Me" onPress={() => { }}/>);
|
|
7
7
|
expect(getByText('Press Me')).toBeTruthy();
|
|
8
8
|
});
|
|
9
9
|
it('calls onPress when pressed', () => {
|
|
10
10
|
const onPressMock = jest.fn();
|
|
11
|
-
const { getByText } = render(<
|
|
11
|
+
const { getByText } = render(<Button text="Press Me" onPress={onPressMock}/>);
|
|
12
12
|
fireEvent.press(getByText('Press Me'));
|
|
13
13
|
expect(onPressMock).toHaveBeenCalledTimes(1);
|
|
14
14
|
});
|
|
15
15
|
it('does not call onPress when disabled', () => {
|
|
16
16
|
const onPressMock = jest.fn();
|
|
17
|
-
const { getByText } = render(<
|
|
17
|
+
const { getByText } = render(<Button text="Press Me" onPress={onPressMock} disabled/>);
|
|
18
18
|
fireEvent.press(getByText('Press Me'));
|
|
19
19
|
expect(onPressMock).not.toHaveBeenCalled();
|
|
20
20
|
});
|
|
21
21
|
it('shows loading indicator when fetching', () => {
|
|
22
|
-
const { queryByText, getByTestId } = render(<
|
|
22
|
+
const { queryByText, getByTestId } = render(<Button text="Press Me" onPress={() => { }} fetching testID="button"/>);
|
|
23
23
|
expect(queryByText('Press Me')).toBeNull();
|
|
24
24
|
});
|
|
25
25
|
it('debounces multiple rapid presses', () => {
|
|
26
26
|
jest.useFakeTimers();
|
|
27
27
|
const onPressMock = jest.fn();
|
|
28
|
-
const { getByText } = render(<
|
|
28
|
+
const { getByText } = render(<Button text="Press Me" onPress={onPressMock} debounceTime={1000}/>);
|
|
29
29
|
const button = getByText('Press Me');
|
|
30
30
|
fireEvent.press(button);
|
|
31
31
|
fireEvent.press(button);
|
|
@@ -37,7 +37,7 @@ describe('YasButton', () => {
|
|
|
37
37
|
jest.useRealTimers();
|
|
38
38
|
});
|
|
39
39
|
it('applies custom primary color', () => {
|
|
40
|
-
const { getByTestId } = render(<
|
|
40
|
+
const { getByTestId } = render(<Button text="Press Me" onPress={() => { }} primaryColor="#FF0000" testID="custom-button"/>);
|
|
41
41
|
const button = getByTestId('custom-button');
|
|
42
42
|
const flattenedStyle = Array.isArray(button.props.style)
|
|
43
43
|
? button.props.style.flat().reduce((acc, style) => ({ ...acc, ...style }), {})
|
|
@@ -46,7 +46,7 @@ describe('YasButton', () => {
|
|
|
46
46
|
});
|
|
47
47
|
it('renders without text when only icon is provided', () => {
|
|
48
48
|
const mockIcon = { uri: 'https://example.com/icon.png' };
|
|
49
|
-
const { queryByText } = render(<
|
|
49
|
+
const { queryByText } = render(<Button onPress={() => { }} icon={mockIcon}/>);
|
|
50
50
|
expect(queryByText('Press Me')).toBeNull();
|
|
51
51
|
});
|
|
52
52
|
});
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
3
|
-
export type {
|
|
4
|
-
declare const _default: React.NamedExoticComponent<
|
|
2
|
+
import { ButtonProps } from './type';
|
|
3
|
+
export type { ButtonProps };
|
|
4
|
+
declare const _default: React.NamedExoticComponent<ButtonProps>;
|
|
5
5
|
export default _default;
|