@hero-design/rn 8.59.0 → 8.60.1-alpha.0
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/.turbo/turbo-build.log +1 -1
- package/CHANGELOG.md +15 -0
- package/es/index.js +240 -116
- package/lib/index.js +240 -116
- package/package.json +2 -2
- package/src/components/AnimatedScroller/AnimatedFAB.tsx +99 -49
- package/src/components/AnimatedScroller/AnimatedScrollable.tsx +18 -3
- package/src/components/AnimatedScroller/__tests__/ScrollablesWithFAB.spec.tsx +30 -9
- package/src/components/AnimatedScroller/__tests__/__snapshots__/ScrollablesWithFAB.spec.tsx.snap +474 -447
- package/src/components/FAB/ActionGroup/ActionItem.tsx +3 -1
- package/src/components/FAB/ActionGroup/__tests__/__snapshots__/index.spec.tsx.snap +216 -211
- package/src/components/FAB/ActionGroup/index.tsx +34 -28
- package/src/components/FAB/FAB.tsx +102 -41
- package/src/components/FAB/StyledFAB.tsx +10 -8
- package/src/components/FAB/__tests__/__snapshots__/StyledFAB.spec.tsx.snap +34 -38
- package/src/components/FAB/__tests__/__snapshots__/index.spec.tsx.snap +191 -170
- package/src/components/Radio/Radio.tsx +16 -4
- package/src/components/Radio/RadioGroup.tsx +10 -3
- package/src/components/Radio/StyledRadio.tsx +20 -3
- package/src/components/Radio/__tests__/Radio.spec.tsx +46 -13
- package/src/components/Radio/__tests__/RadioGroup.spec.tsx +40 -7
- package/src/components/Radio/__tests__/__snapshots__/Radio.spec.tsx.snap +446 -77
- package/src/components/Radio/__tests__/__snapshots__/RadioGroup.spec.tsx.snap +946 -112
- package/src/components/Radio/types.ts +6 -1
- package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +8 -2
- package/src/theme/components/radio.ts +8 -2
- package/types/components/AnimatedScroller/AnimatedFAB.d.ts +3 -1
- package/types/components/AnimatedScroller/AnimatedScrollable.d.ts +1 -1
- package/types/components/FAB/StyledFAB.d.ts +4 -6
- package/types/components/Radio/Radio.d.ts +9 -1
- package/types/components/Radio/RadioGroup.d.ts +5 -1
- package/types/components/Radio/StyledRadio.d.ts +11 -1
- package/types/components/Radio/index.d.ts +1 -1
- package/types/components/Radio/types.d.ts +1 -0
- package/types/theme/components/radio.d.ts +7 -1
|
@@ -7,8 +7,9 @@ import ActionGroup, {
|
|
|
7
7
|
} from '../FAB/ActionGroup';
|
|
8
8
|
import { FABHandles, FABProps } from '../FAB/FAB';
|
|
9
9
|
|
|
10
|
-
const
|
|
11
|
-
const
|
|
10
|
+
const LAST_BREAKPOINT = 100;
|
|
11
|
+
const MIDDLE_BREAKPOINT = 250;
|
|
12
|
+
const MAX_ANIMATABLE_SCROLL_DISTANCE = 400;
|
|
12
13
|
const REF_ACTIONS_BY_COMPONENT = {
|
|
13
14
|
FAB: {
|
|
14
15
|
show: 'show',
|
|
@@ -25,25 +26,36 @@ const REF_ACTIONS_BY_COMPONENT = {
|
|
|
25
26
|
interface AnimatedFABProps {
|
|
26
27
|
fabProps: FABProps | ActionGroupProps;
|
|
27
28
|
contentOffsetY: Animated.Value;
|
|
29
|
+
contentHeight: Animated.Value;
|
|
30
|
+
layoutHeight: Animated.Value;
|
|
28
31
|
}
|
|
29
32
|
|
|
30
|
-
const AnimatedFAB = ({
|
|
33
|
+
const AnimatedFAB = ({
|
|
34
|
+
fabProps,
|
|
35
|
+
contentOffsetY,
|
|
36
|
+
contentHeight,
|
|
37
|
+
layoutHeight,
|
|
38
|
+
}: AnimatedFABProps) => {
|
|
31
39
|
const component = 'items' in fabProps ? 'ActionGroup' : 'FAB';
|
|
32
|
-
const [currentScrollDirection, setCurrentScrollDirection] = React.useState<
|
|
33
|
-
'up' | 'down'
|
|
34
|
-
>('down');
|
|
35
|
-
const [lastScrollY, setLastScrollY] = React.useState(0);
|
|
36
|
-
const [fabState, setFabState] = React.useState<'show' | 'hide' | 'collapse'>(
|
|
37
|
-
'show'
|
|
38
|
-
);
|
|
39
|
-
const [remainingScrollOffset, setRemainingScrollOffset] = React.useState(
|
|
40
|
-
SHOW_AND_HIDE_BREAKPOINT
|
|
41
|
-
);
|
|
42
40
|
const ref = React.useRef<FABHandles & ActionGroupHandles>(null);
|
|
41
|
+
const currentContentHeight = React.useRef(0);
|
|
42
|
+
const currentLayoutHeight = React.useRef(0);
|
|
43
|
+
|
|
44
|
+
/** fabState is used to avoid calling duplicated animations. */
|
|
45
|
+
const fabState = React.useRef<'show' | 'hide' | 'collapse'>('show');
|
|
46
|
+
|
|
47
|
+
/** remainingScrollOffset determines whether to animate the FAB. */
|
|
48
|
+
const remainingScrollOffset = React.useRef(MAX_ANIMATABLE_SCROLL_DISTANCE);
|
|
49
|
+
|
|
50
|
+
/** currentScrollDirection is used to determine the scroll direction. */
|
|
51
|
+
const currentScrollDirection = React.useRef<'up' | 'down'>('down');
|
|
52
|
+
|
|
53
|
+
/** lastScrollY is the scrollY from the preview scroll event. */
|
|
54
|
+
const lastScrollY = React.useRef(0);
|
|
43
55
|
|
|
44
56
|
const animateFab = React.useCallback(
|
|
45
57
|
(newState: 'show' | 'hide' | 'collapse') => {
|
|
46
|
-
if (fabState !== newState) {
|
|
58
|
+
if (fabState.current !== newState) {
|
|
47
59
|
if (newState === 'show') {
|
|
48
60
|
ref.current?.[REF_ACTIONS_BY_COMPONENT[component].show]();
|
|
49
61
|
} else if (newState === 'hide') {
|
|
@@ -51,49 +63,87 @@ const AnimatedFAB = ({ fabProps, contentOffsetY }: AnimatedFABProps) => {
|
|
|
51
63
|
} else {
|
|
52
64
|
ref.current?.[REF_ACTIONS_BY_COMPONENT[component].collapse]();
|
|
53
65
|
}
|
|
54
|
-
|
|
66
|
+
fabState.current = newState;
|
|
55
67
|
}
|
|
56
68
|
},
|
|
57
|
-
[
|
|
69
|
+
[component]
|
|
58
70
|
);
|
|
59
71
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (offsetDiff === SHOW_AND_HIDE_BREAKPOINT) {
|
|
80
|
-
animateFab(isScrollingDown ? 'show' : 'hide');
|
|
81
|
-
} else if (
|
|
82
|
-
offsetDiff <= SHOW_AND_HIDE_BREAKPOINT &&
|
|
83
|
-
offsetDiff > COLLAPSE_BREAKPOINT
|
|
72
|
+
React.useEffect(() => {
|
|
73
|
+
contentHeight.addListener(({ value }) => {
|
|
74
|
+
if (value > 0 && value !== currentContentHeight.current) {
|
|
75
|
+
currentContentHeight.current = value;
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
layoutHeight.addListener(({ value }) => {
|
|
80
|
+
if (value > 0 && value !== currentLayoutHeight.current) {
|
|
81
|
+
currentLayoutHeight.current = value;
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Listen to ScrollView's contentOffsetY value
|
|
86
|
+
contentOffsetY.addListener(({ value }) => {
|
|
87
|
+
if (
|
|
88
|
+
value < 0 ||
|
|
89
|
+
// Prevent calling the function if the scroll is not significant
|
|
90
|
+
(value > 0 && Math.abs(value - lastScrollY.current) < 5)
|
|
84
91
|
) {
|
|
85
|
-
|
|
86
|
-
} else if (offsetDiff <= COLLAPSE_BREAKPOINT) {
|
|
87
|
-
animateFab(isScrollingDown ? 'hide' : 'show');
|
|
92
|
+
return;
|
|
88
93
|
}
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
// Scroll up to top, bouncing included.
|
|
96
|
+
if (value === 0 && lastScrollY.current !== 0) {
|
|
97
|
+
animateFab('show');
|
|
98
|
+
}
|
|
93
99
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
100
|
+
const newScrollDirection = value >= lastScrollY.current ? 'down' : 'up';
|
|
101
|
+
|
|
102
|
+
if (newScrollDirection !== currentScrollDirection.current) {
|
|
103
|
+
// If scroll direction changes, reset all values
|
|
104
|
+
currentScrollDirection.current = newScrollDirection;
|
|
105
|
+
remainingScrollOffset.current = MAX_ANIMATABLE_SCROLL_DISTANCE;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const hasReachedBottom =
|
|
109
|
+
value + currentLayoutHeight.current >= currentContentHeight.current;
|
|
110
|
+
|
|
111
|
+
// Scroll down to bottom, bouncing included.
|
|
112
|
+
if (hasReachedBottom) {
|
|
113
|
+
animateFab('hide');
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (remainingScrollOffset.current) {
|
|
118
|
+
const offsetDiff = Math.round(
|
|
119
|
+
Math.max(Math.abs(value - lastScrollY.current), 0)
|
|
120
|
+
);
|
|
121
|
+
|
|
122
|
+
const newRemainingScrollOffset = Math.max(
|
|
123
|
+
remainingScrollOffset.current - offsetDiff,
|
|
124
|
+
0
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
if (newRemainingScrollOffset <= LAST_BREAKPOINT) {
|
|
128
|
+
animateFab(
|
|
129
|
+
currentScrollDirection.current === 'down' ? 'hide' : 'show'
|
|
130
|
+
);
|
|
131
|
+
} else if (newRemainingScrollOffset <= MIDDLE_BREAKPOINT) {
|
|
132
|
+
animateFab('collapse');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
remainingScrollOffset.current = newRemainingScrollOffset;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
lastScrollY.current = value;
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
return () => {
|
|
142
|
+
contentOffsetY.removeAllListeners();
|
|
143
|
+
contentHeight.removeAllListeners();
|
|
144
|
+
layoutHeight.removeAllListeners();
|
|
145
|
+
};
|
|
146
|
+
}, [contentHeight, contentOffsetY, layoutHeight]);
|
|
97
147
|
|
|
98
148
|
return component === 'FAB' ? (
|
|
99
149
|
<FAB ref={ref} {...(fabProps as FABProps)} />
|
|
@@ -5,9 +5,9 @@ import {
|
|
|
5
5
|
ScrollViewProps as RnScrollViewProps,
|
|
6
6
|
SectionListProps,
|
|
7
7
|
} from 'react-native';
|
|
8
|
+
import { ActionGroupProps } from '../FAB/ActionGroup';
|
|
8
9
|
import { FABProps } from '../FAB/FAB';
|
|
9
10
|
import AnimatedFAB from './AnimatedFAB';
|
|
10
|
-
import { ActionGroupProps } from '../FAB/ActionGroup';
|
|
11
11
|
|
|
12
12
|
export interface AnimatedScrollerProps<T> {
|
|
13
13
|
/**
|
|
@@ -27,6 +27,8 @@ function AnimatedScroller<T>({
|
|
|
27
27
|
fabProps,
|
|
28
28
|
}: AnimatedScrollerProps<T>) {
|
|
29
29
|
const contentOffsetY = React.useRef(new Animated.Value(0)).current;
|
|
30
|
+
const contentHeight = React.useRef(new Animated.Value(0)).current;
|
|
31
|
+
const layoutHeight = React.useRef(new Animated.Value(0)).current;
|
|
30
32
|
|
|
31
33
|
// Common props for all ScrollView, FlatList and SectionList.
|
|
32
34
|
const { onScroll, scrollEventThrottle } = ScrollComponent.props;
|
|
@@ -37,7 +39,15 @@ function AnimatedScroller<T>({
|
|
|
37
39
|
...ScrollComponent.props,
|
|
38
40
|
scrollEventThrottle: scrollEventThrottle || 100,
|
|
39
41
|
onScroll: Animated.event(
|
|
40
|
-
[
|
|
42
|
+
[
|
|
43
|
+
{
|
|
44
|
+
nativeEvent: {
|
|
45
|
+
contentOffset: { y: contentOffsetY },
|
|
46
|
+
contentSize: { height: contentHeight },
|
|
47
|
+
layoutMeasurement: { height: layoutHeight },
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
],
|
|
41
51
|
{
|
|
42
52
|
useNativeDriver: false,
|
|
43
53
|
listener: onScroll,
|
|
@@ -46,7 +56,12 @@ function AnimatedScroller<T>({
|
|
|
46
56
|
})}
|
|
47
57
|
|
|
48
58
|
{!!fabProps && (
|
|
49
|
-
<AnimatedFAB
|
|
59
|
+
<AnimatedFAB
|
|
60
|
+
fabProps={fabProps}
|
|
61
|
+
contentOffsetY={contentOffsetY}
|
|
62
|
+
contentHeight={contentHeight}
|
|
63
|
+
layoutHeight={layoutHeight}
|
|
64
|
+
/>
|
|
50
65
|
)}
|
|
51
66
|
</>
|
|
52
67
|
);
|
|
@@ -125,17 +125,28 @@ describe('Scrollables With FAB', () => {
|
|
|
125
125
|
const fabProps =
|
|
126
126
|
fabComponent === 'FAB' ? defaultFabProps : defaultActionGroupProps;
|
|
127
127
|
|
|
128
|
+
const scrollConfig = {
|
|
129
|
+
contentSize: { height: 2000 },
|
|
130
|
+
layoutMeasurement: { height: 700, width: 400 },
|
|
131
|
+
};
|
|
132
|
+
|
|
128
133
|
const { getByText, queryByText, getByTestId, queryByTestId } =
|
|
129
134
|
renderWithTheme(<ScrollComponent fabProps={fabProps} />);
|
|
130
135
|
|
|
136
|
+
fireEvent.scroll(getByTestId('scrollable-with-fab'), {
|
|
137
|
+
nativeEvent: {
|
|
138
|
+
...scrollConfig,
|
|
139
|
+
contentOffset: { y: 50 },
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
131
143
|
// Scrolling down
|
|
132
144
|
expect(getByText('Shout out')).toBeDefined();
|
|
133
145
|
|
|
134
146
|
fireEvent.scroll(getByTestId('scrollable-with-fab'), {
|
|
135
147
|
nativeEvent: {
|
|
136
|
-
|
|
137
|
-
contentOffset: { y:
|
|
138
|
-
layoutMeasurement: { height: 2000, width: 400 },
|
|
148
|
+
...scrollConfig,
|
|
149
|
+
contentOffset: { y: 150 },
|
|
139
150
|
},
|
|
140
151
|
});
|
|
141
152
|
|
|
@@ -145,9 +156,8 @@ describe('Scrollables With FAB', () => {
|
|
|
145
156
|
|
|
146
157
|
fireEvent.scroll(getByTestId('scrollable-with-fab'), {
|
|
147
158
|
nativeEvent: {
|
|
148
|
-
|
|
149
|
-
contentOffset: { y:
|
|
150
|
-
layoutMeasurement: { height: 2000, width: 400 },
|
|
159
|
+
...scrollConfig,
|
|
160
|
+
contentOffset: { y: 500 },
|
|
151
161
|
},
|
|
152
162
|
});
|
|
153
163
|
|
|
@@ -158,15 +168,26 @@ describe('Scrollables With FAB', () => {
|
|
|
158
168
|
// Scrolling up
|
|
159
169
|
fireEvent.scroll(getByTestId('scrollable-with-fab'), {
|
|
160
170
|
nativeEvent: {
|
|
161
|
-
|
|
162
|
-
contentOffset: { y: -
|
|
163
|
-
layoutMeasurement: { height: 2000, width: 400 },
|
|
171
|
+
...scrollConfig,
|
|
172
|
+
contentOffset: { y: -150 },
|
|
164
173
|
},
|
|
165
174
|
});
|
|
166
175
|
|
|
167
176
|
// Collapsed
|
|
168
177
|
expect(queryByText('Shout out')).toBeNull();
|
|
169
178
|
expect(getByTestId('animated-fab-icon')).toBeDefined();
|
|
179
|
+
|
|
180
|
+
// Scrolling up to top
|
|
181
|
+
fireEvent.scroll(getByTestId('scrollable-with-fab'), {
|
|
182
|
+
nativeEvent: {
|
|
183
|
+
...scrollConfig,
|
|
184
|
+
contentOffset: { y: 0 },
|
|
185
|
+
},
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
// Expanded
|
|
189
|
+
expect(getByText('Shout out')).toBeDefined();
|
|
190
|
+
expect(getByTestId('styled-fab-icon')).toBeDefined();
|
|
170
191
|
}
|
|
171
192
|
);
|
|
172
193
|
});
|