@hero-design/rn 7.1.1 → 7.1.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.
Files changed (36) hide show
  1. package/.eslintrc.json +4 -3
  2. package/es/index.js +283 -113
  3. package/lib/index.js +282 -111
  4. package/package.json +4 -3
  5. package/playground/components/BottomNavigation.tsx +69 -0
  6. package/playground/components/Card.tsx +149 -70
  7. package/playground/index.tsx +3 -0
  8. package/rollup.config.js +1 -1
  9. package/src/components/BottomNavigation/StyledBottomNavigation.tsx +58 -0
  10. package/src/components/BottomNavigation/__tests__/BottomNavigation.spec.tsx +95 -0
  11. package/src/components/BottomNavigation/__tests__/__snapshots__/BottomNavigation.spec.tsx.snap +315 -0
  12. package/src/components/BottomNavigation/index.tsx +169 -0
  13. package/src/components/Icon/utils.ts +6 -0
  14. package/src/components/Typography/Text/StyledText.tsx +5 -1
  15. package/src/components/Typography/Text/index.tsx +1 -1
  16. package/src/index.ts +2 -0
  17. package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +16 -0
  18. package/src/theme/components/bottomNavigation.ts +23 -0
  19. package/src/theme/components/typography.ts +1 -0
  20. package/src/theme/index.ts +3 -0
  21. package/src/utils/helpers.ts +4 -0
  22. package/types/playground/components/BottomNavigation.d.ts +2 -0
  23. package/types/src/components/BottomNavigation/StyledBottomNavigation.d.ts +17 -0
  24. package/types/src/components/BottomNavigation/__tests__/BottomNavigation.spec.d.ts +1 -0
  25. package/types/src/components/BottomNavigation/index.d.ts +40 -0
  26. package/types/src/components/Icon/utils.d.ts +2 -0
  27. package/types/src/components/Typography/Text/StyledText.d.ts +1 -1
  28. package/types/src/components/Typography/Text/index.d.ts +1 -1
  29. package/types/src/index.d.ts +2 -1
  30. package/types/src/theme/components/bottomNavigation.d.ts +17 -0
  31. package/types/src/theme/components/typography.d.ts +1 -0
  32. package/types/src/theme/index.d.ts +2 -0
  33. package/types/src/utils/helpers.d.ts +2 -0
  34. package/.expo/README.md +0 -15
  35. package/.expo/packager-info.json +0 -10
  36. package/.expo/settings.json +0 -10
@@ -0,0 +1,69 @@
1
+ import React from 'react';
2
+ import { View } from 'react-native';
3
+ import { Tab } from '../../src/components/BottomNavigation';
4
+ import { BottomNavigation, Typography } from '../../src/index';
5
+
6
+ const HomeScreen = () => (
7
+ <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
8
+ <Typography.Text>Home Screen</Typography.Text>
9
+ </View>
10
+ );
11
+
12
+ const FeedScreen = () => (
13
+ <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
14
+ <Typography.Text>Feed Screen</Typography.Text>
15
+ </View>
16
+ );
17
+
18
+ const AlertsScreen = () => (
19
+ <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
20
+ <Typography.Text>Alerts Screen</Typography.Text>
21
+ </View>
22
+ );
23
+
24
+ const ProfileScreen = () => (
25
+ <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
26
+ <Typography.Text>Profile Screen</Typography.Text>
27
+ </View>
28
+ );
29
+
30
+ const tabs: Tab<number>[] = [
31
+ {
32
+ key: 0,
33
+ title: 'Home',
34
+ icon: 'home',
35
+ component: <HomeScreen />,
36
+ },
37
+ {
38
+ key: 1,
39
+ title: 'Feed',
40
+ icon: 'speaker',
41
+ component: <FeedScreen />,
42
+ },
43
+ {
44
+ key: 2,
45
+ title: 'Alerts',
46
+ icon: 'bell',
47
+ component: <AlertsScreen />,
48
+ },
49
+ {
50
+ key: 3,
51
+ title: 'Profile',
52
+ icon: 'user',
53
+ component: <ProfileScreen />,
54
+ },
55
+ ];
56
+
57
+ const MyComponent = () => {
58
+ const [selectedTabKey, setSelectedTabKey] = React.useState(0);
59
+
60
+ return (
61
+ <BottomNavigation
62
+ onChange={newTabKey => setSelectedTabKey(newTabKey)}
63
+ selectedTabKey={selectedTabKey}
64
+ tabs={tabs}
65
+ />
66
+ );
67
+ };
68
+
69
+ export default MyComponent;
@@ -1,50 +1,133 @@
1
1
  import React, { ComponentProps } from 'react';
2
- import { View, ViewProps, StyleSheet } from 'react-native';
3
- import { palette } from '@hero-design/colors';
4
2
  import {
5
- useTheme,
6
- scale,
7
- Card,
8
- Typography,
9
- Icon,
10
- Divider,
11
- } from '../../src/index';
12
-
13
- const Screen = View;
3
+ View,
4
+ ViewProps,
5
+ StyleSheet,
6
+ TouchableOpacity,
7
+ ScrollView,
8
+ } from 'react-native';
9
+ import { palette } from '@hero-design/colors';
10
+ import { scale, Card, Typography, Icon, Divider, theme } from '../../src/index';
14
11
 
15
12
  type DashboardCardProps = ViewProps & {
16
13
  icon: ComponentProps<typeof Icon>['icon'];
17
14
  title: string;
18
15
  subtitle: string;
19
16
  };
17
+ type PendingItemProps = {
18
+ count: number;
19
+ label: string;
20
+ onPress?: () => void;
21
+ wrapperTestID?: string;
22
+ countTestID?: string;
23
+ };
24
+
25
+ const PendingItem = ({
26
+ count,
27
+ label,
28
+ onPress,
29
+ wrapperTestID,
30
+ countTestID,
31
+ }: Omit<PendingItemProps, 'isLoading'>) => (
32
+ <TouchableOpacity
33
+ onPress={onPress}
34
+ style={{
35
+ paddingHorizontal: theme.space.small,
36
+ flex: 1,
37
+ }}
38
+ testID={wrapperTestID}
39
+ >
40
+ <Typography.Text
41
+ fontSize="xlarge"
42
+ fontWeight="semi-bold"
43
+ style={{
44
+ textAlign: 'center',
45
+ paddingBottom: theme.space.small,
46
+ color: palette.redLight30,
47
+ }}
48
+ testID={countTestID}
49
+ >
50
+ {count}
51
+ </Typography.Text>
52
+ <Typography.Text
53
+ style={{ textAlign: 'center', color: palette.greyDark75 }}
54
+ fontSize="large"
55
+ >
56
+ {label}
57
+ </Typography.Text>
58
+ </TouchableOpacity>
59
+ );
60
+
61
+ const ApprovalsCardLayout = ({ style }: ViewProps) => (
62
+ <Card
63
+ style={StyleSheet.flatten([
64
+ {
65
+ backgroundColor: palette.greyLight90,
66
+ marginBottom: theme.space.medium,
67
+ paddingTop: 0,
68
+ paddingRight: 0,
69
+ paddingBottom: 0,
70
+ paddingLeft: 0,
71
+ },
72
+ style,
73
+ ])}
74
+ >
75
+ <View
76
+ style={{
77
+ backgroundColor: palette.redLight60,
78
+ display: 'flex',
79
+ flexDirection: 'row',
80
+ padding: theme.space.medium,
81
+ marginBottom: theme.space.medium,
82
+ }}
83
+ >
84
+ <Icon
85
+ icon="multiple-users"
86
+ style={{
87
+ marginRight: theme.space.small,
88
+ }}
89
+ />
90
+ <Typography.Text fontWeight="semi-bold" fontSize="xlarge">
91
+ Approval card
92
+ </Typography.Text>
93
+ </View>
94
+ <View
95
+ style={{
96
+ flex: 1,
97
+ flexDirection: 'row',
98
+ }}
99
+ >
100
+ <PendingItem count={1} label="Timesheets" />
101
+ <PendingItem count={2} label="Leave Requests" />
102
+ <PendingItem count={7} label="Expense Claims" />
103
+ </View>
104
+ </Card>
105
+ );
20
106
 
21
107
  const DashboardCard = ({
22
108
  style,
23
109
  icon,
24
110
  title,
25
111
  subtitle,
26
- }: DashboardCardProps) => {
27
- const theme = useTheme();
28
- return (
29
- <Card style={style}>
30
- <View style={{ flex: 1, alignItems: 'flex-end' }}>
31
- <Icon icon={icon} />
32
- </View>
33
- <View
34
- style={{
35
- flex: 1,
36
- justifyContent: 'flex-end',
37
- margin: theme.space.small,
38
- }}
39
- >
40
- <Typography.Text fontSize="xlarge" fontWeight="semi-bold">
41
- {title}
42
- </Typography.Text>
43
- <Typography.Text fontSize="medium">{subtitle}</Typography.Text>
44
- </View>
45
- </Card>
46
- );
47
- };
112
+ }: DashboardCardProps) => (
113
+ <Card style={style}>
114
+ <View style={{ flex: 1, alignItems: 'flex-end' }}>
115
+ <Icon icon={icon} />
116
+ </View>
117
+ <View
118
+ style={{
119
+ flex: 1,
120
+ justifyContent: 'flex-end',
121
+ margin: theme.space.small,
122
+ }}
123
+ >
124
+ <Typography.Text fontSize="xlarge" fontWeight="semi-bold">
125
+ {title}
126
+ </Typography.Text>
127
+ <Typography.Text fontSize="medium">{subtitle}</Typography.Text>
128
+ </View>
129
+ </Card>
130
+ );
48
131
 
49
132
  const InductionCard = (props: ViewProps) => (
50
133
  <DashboardCard
@@ -73,7 +156,6 @@ const CertificationsCard = (props: ViewProps) => (
73
156
  );
74
157
 
75
158
  const RostersCard = (props: ViewProps) => {
76
- const theme = useTheme();
77
159
  const { style, ...otherProps } = props;
78
160
  return (
79
161
  <Card
@@ -110,7 +192,6 @@ const RostersCard = (props: ViewProps) => {
110
192
  };
111
193
 
112
194
  const MyLeaveCard = (props: ViewProps) => {
113
- const theme = useTheme();
114
195
  const { style, ...otherProps } = props;
115
196
  return (
116
197
  <Card
@@ -181,41 +262,39 @@ const MyLeaveCard = (props: ViewProps) => {
181
262
  );
182
263
  };
183
264
 
184
- const CardPlayground = () => {
185
- const theme = useTheme();
186
- return (
187
- <Screen style={{ padding: theme.space.medium }}>
188
- <View style={{ flexDirection: 'row', height: scale(216) }}>
189
- <View style={{ flex: 1, marginRight: theme.space.medium }}>
190
- <InductionCard
191
- style={{
192
- flex: 1,
193
- backgroundColor: theme.colors.primaryLight,
194
- }}
195
- />
196
- </View>
197
- <View style={{ flex: 1 }}>
198
- <PoliciesCard
199
- style={{
200
- flex: 1,
201
- backgroundColor: theme.colors.infoLight,
202
- marginBottom: theme.space.medium,
203
- }}
204
- testID="policies"
205
- />
206
- <CertificationsCard
207
- style={{
208
- flex: 1,
209
- backgroundColor: theme.colors.infoLight,
210
- }}
211
- testID="certifications"
212
- />
213
- </View>
265
+ const CardPlayground = () => (
266
+ <ScrollView style={{ padding: theme.space.medium }}>
267
+ <View style={{ flexDirection: 'row', height: scale(216) }}>
268
+ <View style={{ flex: 1, marginRight: theme.space.medium }}>
269
+ <InductionCard
270
+ style={{
271
+ flex: 1,
272
+ backgroundColor: theme.colors.primaryLight,
273
+ }}
274
+ />
214
275
  </View>
215
- <RostersCard style={{ marginTop: theme.space.medium }} />
216
- <MyLeaveCard style={{ marginTop: theme.space.medium }} />
217
- </Screen>
218
- );
219
- };
276
+ <View style={{ flex: 1 }}>
277
+ <PoliciesCard
278
+ style={{
279
+ flex: 1,
280
+ backgroundColor: theme.colors.infoLight,
281
+ marginBottom: theme.space.medium,
282
+ }}
283
+ testID="policies"
284
+ />
285
+ <CertificationsCard
286
+ style={{
287
+ flex: 1,
288
+ backgroundColor: theme.colors.infoLight,
289
+ }}
290
+ testID="certifications"
291
+ />
292
+ </View>
293
+ </View>
294
+ <RostersCard style={{ marginTop: theme.space.medium }} />
295
+ <MyLeaveCard style={{ marginTop: theme.space.medium }} />
296
+ <ApprovalsCardLayout style={{ marginTop: theme.space.medium }} />
297
+ </ScrollView>
298
+ );
220
299
 
221
300
  export default CardPlayground;
@@ -9,6 +9,7 @@ import { useFonts } from 'expo-font';
9
9
  import { SafeAreaView, FlatList, Text } from 'react-native';
10
10
  import { ThemeProvider, theme, useTheme } from '../src/index';
11
11
  import BadgePlayground from './components/Badge';
12
+ import BottomNavigation from './components/BottomNavigation';
12
13
  import CardPlayground from './components/Card';
13
14
  import DividerPlayground from './components/Divider';
14
15
  import IconPlayground from './components/Icon';
@@ -20,6 +21,7 @@ const heroIconFontPath = require('../assets/fonts/hero-icons.ttf');
20
21
  type RootStackParamList = {
21
22
  Home: undefined;
22
23
  Badge: undefined;
24
+ BottomNavigation: undefined;
23
25
  Card: undefined;
24
26
  Divider: undefined;
25
27
  Icon: undefined;
@@ -33,6 +35,7 @@ const Stack = createNativeStackNavigator<RootStackParamList>();
33
35
 
34
36
  const components = [
35
37
  { name: 'Badge', component: BadgePlayground },
38
+ { name: 'BottomNavigation', component: BottomNavigation },
36
39
  { name: 'Card', component: CardPlayground },
37
40
  { name: 'Divider', component: DividerPlayground },
38
41
  { name: 'Icon', component: IconPlayground },
package/rollup.config.js CHANGED
@@ -22,7 +22,7 @@ export default {
22
22
  format: 'esm',
23
23
  },
24
24
  ],
25
- external: ['react', 'react-native'],
25
+ external: ['react', 'react-native', 'react-native-safe-area-context'],
26
26
  plugins: [
27
27
  replace({
28
28
  'process.env.NODE_ENV': JSON.stringify('production'),
@@ -0,0 +1,58 @@
1
+ import { View } from 'react-native';
2
+ import styled from 'styled-components-native';
3
+
4
+ const BottomNavigationTab = styled(View)<{ themeVisibility?: boolean }>`
5
+ flex: 1;
6
+ display: ${({ themeVisibility }) =>
7
+ themeVisibility === false ? 'none' : 'flex'};
8
+ `;
9
+
10
+ const BottomNavigationContainer = styled(View)`
11
+ flex: 1;
12
+ overflow: hidden;
13
+ `;
14
+
15
+ const ContentWrapper = styled(View)`
16
+ flex: 1;
17
+ `;
18
+
19
+ const BottomBarWrapper = styled(View)<{
20
+ themeInsets: { top: number; right: number; bottom: number; left: number };
21
+ }>`
22
+ height: ${({ theme, themeInsets }) =>
23
+ theme.__hd__.bottomNavigation.sizes.height + themeInsets.bottom}px;
24
+ padding-bottom: ${({ themeInsets }) => themeInsets.bottom}px;
25
+ padding-left: ${({ themeInsets }) =>
26
+ Math.max(themeInsets.left, themeInsets.right)}px;
27
+ padding-right: ${({ themeInsets }) =>
28
+ Math.max(themeInsets.left, themeInsets.right)}px;
29
+ background-color: ${({ theme }) =>
30
+ theme.__hd__.bottomNavigation.colors.background};
31
+ shadow-color: ${({ theme }) => theme.__hd__.bottomNavigation.colors.shadow};
32
+ shadow-offset: ${({ theme }) => theme.__hd__.bottomNavigation.shadows.offset};
33
+ shadow-opacity: ${({ theme }) =>
34
+ theme.__hd__.bottomNavigation.shadows.opacity};
35
+ shadow-radius: ${({ theme }) => theme.__hd__.bottomNavigation.shadows.radius};
36
+ elevation: ${({ theme }) => theme.__hd__.bottomNavigation.shadows.elevation};
37
+ `;
38
+
39
+ const BottomBar = styled(View)`
40
+ flex: 1;
41
+ flex-direction: row;
42
+ overflow: hidden;
43
+ align-items: center;
44
+ `;
45
+
46
+ const BottomBarItem = styled(View)`
47
+ flex: 1;
48
+ align-items: center;
49
+ `;
50
+
51
+ export {
52
+ BottomBar,
53
+ BottomBarItem,
54
+ BottomNavigationTab,
55
+ BottomNavigationContainer,
56
+ BottomBarWrapper,
57
+ ContentWrapper,
58
+ };
@@ -0,0 +1,95 @@
1
+ import React, { ComponentProps } from 'react';
2
+ import { Text } from 'react-native';
3
+ import { fireEvent } from '@testing-library/react-native';
4
+ import { SafeAreaProvider } from 'react-native-safe-area-context';
5
+ import renderWithTheme from '../../../testHelpers/renderWithTheme';
6
+ import BottomNavigation, { Tab } from '..';
7
+
8
+ const TestComponent = (
9
+ props: Omit<
10
+ ComponentProps<typeof BottomNavigation>,
11
+ 'selectedTabKey' | 'onChange' | 'tabs'
12
+ >
13
+ ) => {
14
+ const [selectedTabKey, setSelectedTabKey] = React.useState('home');
15
+ const tabs: Tab<string>[] = [
16
+ {
17
+ key: 'home',
18
+ title: 'Home',
19
+ icon: 'home',
20
+ component: <Text>Home Screen</Text>,
21
+ },
22
+ {
23
+ key: 'feed',
24
+ title: 'Feed',
25
+ icon: 'speaker-outlined',
26
+ component: <Text>Feed Screen</Text>,
27
+ },
28
+ {
29
+ key: 'alerts',
30
+ title: 'Alerts',
31
+ icon: 'bell-outlined',
32
+ component: <Text>Alerts Screen</Text>,
33
+ },
34
+ {
35
+ key: 'profile',
36
+ title: 'Profile',
37
+ icon: 'user-outlined',
38
+ component: <Text>Profile Screen</Text>,
39
+ },
40
+ ];
41
+
42
+ return (
43
+ <SafeAreaProvider
44
+ initialMetrics={{
45
+ frame: { x: 0, y: 0, width: 0, height: 0 },
46
+ insets: { top: 0, left: 0, right: 0, bottom: 0 },
47
+ }}
48
+ >
49
+ <BottomNavigation
50
+ {...props}
51
+ tabs={tabs}
52
+ selectedTabKey={selectedTabKey}
53
+ onChange={newTabKey => setSelectedTabKey(newTabKey)}
54
+ />
55
+ </SafeAreaProvider>
56
+ );
57
+ };
58
+
59
+ describe('BottomNavigation', () => {
60
+ it('renders correctly', () => {
61
+ const { getByText, toJSON } = renderWithTheme(<TestComponent />);
62
+
63
+ expect(toJSON()).toMatchSnapshot();
64
+ expect(getByText('Home')).toBeDefined();
65
+ fireEvent.press(getByText('Feed'));
66
+ fireEvent.press(getByText('Alerts'));
67
+ fireEvent.press(getByText('Profile'));
68
+
69
+ // All screens are rendered and component is not unmounted when switching screen.
70
+ expect(getByText('Home Screen')).toBeDefined();
71
+ expect(getByText('Feed Screen')).toBeDefined();
72
+ expect(getByText('Alerts Screen')).toBeDefined();
73
+ expect(getByText('Profile Screen')).toBeDefined();
74
+ });
75
+
76
+ it('renders correctly with renderActiveTabOnly is true', () => {
77
+ const { getByText, queryByText, getByTestId } = renderWithTheme(
78
+ <TestComponent renderActiveTabOnly />
79
+ );
80
+
81
+ // Can switch tab by clicking icon.
82
+ expect(getByText('Home Screen')).toBeDefined();
83
+ fireEvent.press(getByTestId('hero-icon-speaker-outlined'));
84
+ expect(getByText('Feed Screen')).toBeDefined();
85
+ fireEvent.press(getByTestId('hero-icon-bell-outlined'));
86
+ expect(getByText('Alerts Screen')).toBeDefined();
87
+ fireEvent.press(getByTestId('hero-icon-user-outlined'));
88
+ expect(getByText('Profile Screen')).toBeDefined();
89
+
90
+ // Only render selected screen, others are unmounted.
91
+ expect(queryByText('Home Screen')).toBeNull();
92
+ expect(queryByText('Feed Screen')).toBeNull();
93
+ expect(queryByText('Alerts Screen')).toBeNull();
94
+ });
95
+ });