@hero-design/rn 8.26.0 → 8.26.1

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.
@@ -10,7 +10,7 @@ describe('ActionGroup', () => {
10
10
  ${true}
11
11
  ${false}
12
12
  `('has active $active', ({ active }) => {
13
- const { toJSON, getByTestId, getByText } = renderWithTheme(
13
+ const { toJSON, queryByTestId, queryByText } = renderWithTheme(
14
14
  <ActionGroup
15
15
  fabTitle="Shout out"
16
16
  active={active}
@@ -39,25 +39,19 @@ describe('ActionGroup', () => {
39
39
 
40
40
  expect(toJSON()).toMatchSnapshot();
41
41
 
42
- expect(getByText('What would you like to create?')).toBeDefined();
43
- expect(getByText('Shout out')).toBeDefined();
44
- expect(getByTestId('speaker-action-item')).toBeDefined();
45
- expect(getByTestId('target-action-item')).toBeDefined();
46
- expect(getByTestId('plane-action-item')).toBeDefined();
47
- expect(getByTestId('health-bag-action-item')).toBeDefined();
42
+ expect(queryByText('What would you like to create?')).toBeDefined();
43
+ expect(queryByText('Shout out')).toBeDefined();
44
+ expect(queryByTestId('speaker-action-item')).toBeDefined();
45
+ expect(queryByTestId('target-action-item')).toBeDefined();
46
+ expect(queryByTestId('plane-action-item')).toBeDefined();
47
+ expect(queryByTestId('health-bag-action-item')).toBeDefined();
48
48
 
49
49
  if (active) {
50
50
  // verify action group appears
51
- expect(getByTestId('action-group')).toHaveStyle({
51
+ expect(queryByTestId('action-group')).toHaveStyle({
52
52
  transform: [{ translateX: 0 }],
53
53
  });
54
- expect(getByTestId('back-drop')).toHaveProp('pointerEvents', 'auto');
55
- } else {
56
- // verify action group disappears
57
- expect(getByTestId('action-group')).toHaveStyle({
58
- transform: [{ translateX: 400 }],
59
- });
60
- expect(getByTestId('back-drop')).toHaveProp('pointerEvents', 'box-none');
54
+ expect(queryByTestId('back-drop')).toHaveProp('pointerEvents', 'auto');
61
55
  }
62
56
  });
63
57
 
@@ -1,16 +1,18 @@
1
- import React, { useEffect, useRef } from 'react';
2
- import { Animated, Easing, Platform, View } from 'react-native';
1
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
3
2
  import type { StyleProp, ViewStyle } from 'react-native';
3
+ import { Animated, Easing, Modal, Platform, View } from 'react-native';
4
+ import { useTheme } from '../../../theme';
5
+ import type { IconName } from '../../Icon';
6
+ import type { ActionItemProps } from './ActionItem';
4
7
  import ActionItem from './ActionItem';
5
8
  import {
9
+ StyledActionGroupContainer,
6
10
  StyledBackdrop,
7
11
  StyledContainer,
8
12
  StyledFAB,
9
13
  StyledHeaderText,
10
- StyledActionGroupContainer,
14
+ StyledModalView,
11
15
  } from './StyledActionGroup';
12
- import type { IconName } from '../../Icon';
13
- import type { ActionItemProps } from './ActionItem';
14
16
 
15
17
  type ActionItemsContainerProps = {
16
18
  style?: StyleProp<ViewStyle>;
@@ -74,6 +76,7 @@ export interface ActionGroupProps {
74
76
 
75
77
  testID?: string;
76
78
  }
79
+
77
80
  const ActionGroup = ({
78
81
  headerTitle,
79
82
  onPress,
@@ -84,18 +87,48 @@ const ActionGroup = ({
84
87
  fabTitle,
85
88
  fabIcon = 'add',
86
89
  }: ActionGroupProps) => {
90
+ const theme = useTheme();
91
+ // Internal state to control the animation of the action group
92
+ const [visible, setVisibility] = useState(active);
87
93
  const tranlateXAnimation = useRef<Animated.Value>(
88
94
  new Animated.Value(active ? 1 : 0)
89
95
  );
96
+
97
+ useEffect(() => {
98
+ if (active && !visible) {
99
+ setVisibility(true);
100
+ }
101
+ }, [active]);
102
+
90
103
  useEffect(() => {
104
+ if (active) {
105
+ const animation = Animated.timing(tranlateXAnimation.current, {
106
+ toValue: 1,
107
+ useNativeDriver: Platform.OS === 'ios' || Platform.OS === 'android',
108
+ easing: Easing.inOut(Easing.cubic),
109
+ });
110
+
111
+ animation.start();
112
+ }
113
+ }, [active]);
114
+
115
+ // Make sure the animation finishes running before closing the modal
116
+ const onInternalFABPress = useCallback(() => {
117
+ if (!onPress) {
118
+ return;
119
+ }
120
+
91
121
  const animation = Animated.timing(tranlateXAnimation.current, {
92
- toValue: active ? 1 : 0,
122
+ toValue: 0,
93
123
  useNativeDriver: Platform.OS === 'ios' || Platform.OS === 'android',
94
124
  easing: Easing.inOut(Easing.cubic),
95
125
  });
96
126
 
97
- animation.start();
98
- }, [active]);
127
+ animation.start(() => {
128
+ setVisibility(false);
129
+ onPress();
130
+ });
131
+ }, [visible]);
99
132
 
100
133
  const interpolatedTranlateXAnimation = tranlateXAnimation.current.interpolate(
101
134
  {
@@ -117,34 +150,63 @@ const ActionGroup = ({
117
150
 
118
151
  return (
119
152
  <StyledContainer testID={testID} pointerEvents="box-none" style={style}>
120
- <StyledBackdrop
121
- pointerEvents={active ? 'auto' : 'box-none'}
122
- testID="back-drop"
123
- style={{ opacity: interpolatedBackdropOpacityAnimation }}
124
- />
125
- <StyledActionGroupContainer
126
- pointerEvents={active ? 'auto' : 'none'}
127
- testID="action-group"
128
- style={{
129
- opacity: interpolatedActionGroupOpacityAnimation,
130
- transform: [{ translateX: interpolatedTranlateXAnimation }],
131
- }}
153
+ <Modal
154
+ visible={visible}
155
+ transparent
156
+ statusBarTranslucent
157
+ animationType="none"
132
158
  >
133
- {!!headerTitle && (
134
- <StyledHeaderText testID="header-text">
135
- {headerTitle}
136
- </StyledHeaderText>
137
- )}
138
- <ActionItemsListComponent items={items} />
139
- </StyledActionGroupContainer>
140
- <StyledFAB
141
- testID="fab"
142
- icon={fabIcon}
143
- onPress={onPress}
144
- animated
145
- active={active}
146
- title={fabTitle}
147
- />
159
+ <StyledBackdrop
160
+ style={{
161
+ opacity: interpolatedBackdropOpacityAnimation,
162
+ }}
163
+ testID="back-drop"
164
+ pointerEvents={active ? 'auto' : 'box-none'}
165
+ />
166
+
167
+ <StyledModalView>
168
+ <StyledActionGroupContainer
169
+ pointerEvents={active ? 'auto' : 'none'}
170
+ testID="action-group"
171
+ style={{
172
+ opacity: interpolatedActionGroupOpacityAnimation,
173
+ transform: [{ translateX: interpolatedTranlateXAnimation }],
174
+ }}
175
+ >
176
+ {!!headerTitle && (
177
+ <StyledHeaderText testID="header-text">
178
+ {headerTitle}
179
+ </StyledHeaderText>
180
+ )}
181
+ <ActionItemsListComponent items={items} />
182
+ </StyledActionGroupContainer>
183
+
184
+ {active && (
185
+ <StyledFAB
186
+ testID="fab"
187
+ icon={fabIcon}
188
+ onPress={onInternalFABPress}
189
+ animated
190
+ active={active}
191
+ title={fabTitle}
192
+ style={{
193
+ marginBottom: theme.__hd__.fab.space.internalFABMarginBottom,
194
+ }}
195
+ />
196
+ )}
197
+ </StyledModalView>
198
+ </Modal>
199
+
200
+ {!active && (
201
+ <StyledFAB
202
+ testID="fab"
203
+ icon={fabIcon}
204
+ onPress={onPress}
205
+ animated
206
+ active={active}
207
+ title={fabTitle}
208
+ />
209
+ )}
148
210
  </StyledContainer>
149
211
  );
150
212
  };
@@ -512,6 +512,7 @@ Object {
512
512
  "containerPadding": 20,
513
513
  "headerTextMarginBottom": 24,
514
514
  "headerTextMarginRight": 24,
515
+ "internalFABMarginBottom": 24,
515
516
  "titleMarginHorizontal": 8,
516
517
  },
517
518
  },
@@ -58,6 +58,7 @@ const getFABTheme = (theme: GlobalTheme) => {
58
58
  headerTextMarginBottom: theme.space.large,
59
59
  containerPadding: theme.space.large - theme.space.xsmall,
60
60
  titleMarginHorizontal: theme.space.small,
61
+ internalFABMarginBottom: theme.space.large,
61
62
  };
62
63
 
63
64
  const radii = {
@@ -28,4 +28,10 @@ declare const StyledHeaderText: import("@emotion/native").StyledComponent<TextPr
28
28
  theme?: import("@emotion/react").Theme | undefined;
29
29
  as?: import("react").ElementType<any> | undefined;
30
30
  }, {}, {}>;
31
- export { StyledHeaderText, StyledBackdrop, StyledContainer, StyledActionGroupContainer, StyledFAB, };
31
+ declare const StyledModalView: import("@emotion/native").StyledComponent<ViewProps & {
32
+ theme?: import("@emotion/react").Theme | undefined;
33
+ as?: import("react").ElementType<any> | undefined;
34
+ }, {}, {
35
+ ref?: import("react").Ref<View> | undefined;
36
+ }>;
37
+ export { StyledHeaderText, StyledBackdrop, StyledContainer, StyledActionGroupContainer, StyledFAB, StyledModalView, };
@@ -57,6 +57,7 @@ declare const getFABTheme: (theme: GlobalTheme) => {
57
57
  headerTextMarginBottom: number;
58
58
  containerPadding: number;
59
59
  titleMarginHorizontal: number;
60
+ internalFABMarginBottom: number;
60
61
  };
61
62
  };
62
63
  export default getFABTheme;