Skip to content

Commit 1381260

Browse files
ethansharnitzanyiz
andauthored
Hint Refactor (#3486)
* refactor: rename hint rendering methods for clarity * fix: add second hint toggle and adjust hint message positioning * Update src/components/hint/index.tsx Co-authored-by: Nitzan Yizhar <[email protected]> * fix: adjust edge margins and target position thresholds in Hint component * Add minimum width to Hint component for better layout consistency * Convert Hint class component to a function component * Separate old Hint code to another file * export error for hint wrap with asBaseComponent * Extract types to another file * Extract animation logic to useHintAnimation * Add useHintLayout hook to manage hint layout state and measurements * Move HintProps to types * cleanups * Add useHintAccessibility hook for improved accessibility handling * Add useHintPosition hook for dynamic hint positioning logic * Refactor and extract HintMockChildren component * Add HintAnchor component for improved hint rendering and management * Fix render overlay not rendered * Refactor Hint components for improved structure and layout handling * Refactor Hint components to use LayoutRectangle for target layout and improve type definitions * Refactor Hint components to replace TARGET_POSITIONS with TargetAlignments for improved clarity and consistency * Refactor HintAnchor and useHintPosition components to improve type definitions and enhance clarity in layout handling * Refactor useHintPosition hook to improve target position calculations and enhance clarity in hint positioning logic * Refactor useHintPosition hook to remove unused targetLayout prop and optimize target alignment calculations * Refactor useHintLayout hook to accept props directly and improve target layout handling * Update HintsScreen to rename 'Middle Top' to 'Middle Tip' and improve hint key generation for better clarity * Refactor HintBubble import path and clean up useHintPosition hook by removing commented-out code and unnecessary padding logic * Refactor useHintLayout hook to initialize target layout state correctly and handle safe area insets; rename NewHint component to Hint for consistency * Refactor Hint component drivers and tests; update useHintLayout hook to initialize target layout state with targetFrame * Update Hint component snapshots * Refactor useHintPosition hook to unify tip position style variable naming for consistency * Add comment to clarify targetScreenToRelativeOffset calculation in useHintPosition hook * HintsScreen - Update hintKey to include showCustomContent and showReactionStrip for improved uniqueness * Rename useHintAnimation to useHintVisibility and move some code * Fix hint position calculation for RTL --------- Co-authored-by: Nitzan Yizhar <[email protected]>
1 parent 03f26f6 commit 1381260

16 files changed

+3340
-592
lines changed

demo/src/screens/componentScreens/HintsScreen.tsx

+23-2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ type HintScreenProps = {};
1212
export default class HintsScreen extends Component<HintScreenProps> {
1313
state = {
1414
showHint: true,
15+
showSecondHint: false,
1516
useShortMessage: false,
1617
showBottomHint: false,
1718
showIcon: false,
@@ -28,6 +29,10 @@ export default class HintsScreen extends Component<HintScreenProps> {
2829
this.setState({showHint: !this.state.showHint});
2930
};
3031

32+
toggleSecondHint = () => {
33+
this.setState({showSecondHint: !this.state.showSecondHint});
34+
};
35+
3136
toggleHintPosition = () => {
3237
this.setState({
3338
showBottomHint: !this.state.showBottomHint
@@ -92,7 +97,7 @@ export default class HintsScreen extends Component<HintScreenProps> {
9297

9398
{renderMultipleSegmentOptions.call(this, 'Tip Position', 'useSideTip', [
9499
{label: 'Side Tip', value: true},
95-
{label: 'Middle Top', value: false}
100+
{label: 'Middle Tip', value: false}
96101
])}
97102

98103
{renderMultipleSegmentOptions.call(this, 'Hint Position', 'showBottomHint', [
@@ -118,6 +123,7 @@ export default class HintsScreen extends Component<HintScreenProps> {
118123
render() {
119124
const {
120125
showHint,
126+
showSecondHint,
121127
showBottomHint,
122128
showIcon,
123129
targetPosition,
@@ -135,6 +141,8 @@ export default class HintsScreen extends Component<HintScreenProps> {
135141
: 'Add other cool and useful stuff through adding apps to your visitors to enjoy.';
136142
const color = !showCustomContent && showReactionStrip ? {color: Colors.$backgroundDefault} : undefined;
137143

144+
const hintKey = `${useSideTip}-${targetPosition}-${useShortMessage}-${showIcon}-${useTargetFrame}-${showCustomContent}-${showReactionStrip}`;
145+
138146
return (
139147
<View flex>
140148
<View
@@ -164,7 +172,7 @@ export default class HintsScreen extends Component<HintScreenProps> {
164172
// offset={35}
165173
position={showBottomHint ? Hint.positions.BOTTOM : Hint.positions.TOP}
166174
useSideTip={useSideTip}
167-
key={targetPosition}
175+
key={hintKey}
168176
onPress={this.onHintPressed}
169177
targetFrame={useTargetFrame ? targetFrame : undefined}
170178
// borderRadius={BorderRadiuses.br40}
@@ -215,6 +223,19 @@ export default class HintsScreen extends Component<HintScreenProps> {
215223
</View>
216224
</>
217225
)}
226+
227+
<View marginT-100 row center>
228+
{targetPosition !== 'flex-start' && <Text marginH-s3>Text pushing button</Text>}
229+
<Hint
230+
message={'Hint'}
231+
visible={showSecondHint}
232+
onBackgroundPress={this.toggleSecondHint}
233+
useSideTip={false}
234+
>
235+
<Button label="Button" onPress={this.toggleSecondHint}/>
236+
</Hint>
237+
{targetPosition === 'flex-start' && <Text marginH-s3>Text pushing button</Text>}
238+
</View>
218239
</View>
219240

220241
{this.renderOptionsFAB()}
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import {useComponentDriver, ComponentProps} from '../../testkit/new/Component.driver';
2+
import {ModalDriver} from '../modal/Modal.driver.new';
3+
import {ViewDriver} from '../view/View.driver.new';
4+
5+
export const HintDriver = (props: ComponentProps) => {
6+
const driver = useComponentDriver(props);
7+
8+
const hintBubbleDriver = ViewDriver({
9+
renderTree: props.renderTree,
10+
testID: `${props.testID}.message`
11+
});
12+
13+
const modalDriver = ModalDriver({renderTree: props.renderTree, testID: `${props.testID}.message`});
14+
15+
return {
16+
...driver,
17+
getHintBubble: () => hintBubbleDriver,
18+
getModal: () => modalDriver
19+
};
20+
};

src/components/hint/HintAnchor.tsx

+81
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import React from 'react';
2+
import {StyleSheet, type LayoutRectangle} from 'react-native';
3+
4+
import View from '../view';
5+
6+
import {LayoutStyle, HintProps, PaddingsStyle} from './types';
7+
8+
interface HintAnchorProps extends HintProps {
9+
showHint: boolean;
10+
isUsingModal: boolean;
11+
targetLayout?: LayoutRectangle;
12+
hintContainerLayout: LayoutStyle;
13+
hintPadding: PaddingsStyle;
14+
hintAnimatedStyle: any;
15+
}
16+
17+
export default function HintAnchor({
18+
children,
19+
showHint,
20+
isUsingModal,
21+
targetLayout,
22+
containerWidth,
23+
testID,
24+
hintContainerLayout,
25+
hintPadding,
26+
hintAnimatedStyle,
27+
style,
28+
...others
29+
}: HintAnchorProps) {
30+
const renderHintContainer = () => {
31+
if (showHint) {
32+
return (
33+
<View
34+
animated
35+
style={[
36+
{width: containerWidth},
37+
styles.animatedContainer,
38+
hintContainerLayout,
39+
hintPadding,
40+
hintAnimatedStyle
41+
]}
42+
pointerEvents="box-none"
43+
testID={testID}
44+
>
45+
{children}
46+
</View>
47+
);
48+
}
49+
};
50+
51+
return (
52+
<View
53+
{...others}
54+
// Note: this view must be collapsable, don't pass testID or backgroundColor etc'.
55+
collapsable
56+
testID={undefined}
57+
style={[
58+
styles.anchor,
59+
style,
60+
/* containerPosition, */
61+
{left: targetLayout?.x, top: targetLayout?.y},
62+
!isUsingModal && styles.anchorForScreenOverlay
63+
]}
64+
>
65+
{renderHintContainer()}
66+
</View>
67+
);
68+
}
69+
70+
const styles = StyleSheet.create({
71+
anchor: {
72+
position: 'absolute'
73+
},
74+
anchorForScreenOverlay: {
75+
zIndex: 10,
76+
elevation: 10
77+
},
78+
animatedContainer: {
79+
position: 'absolute'
80+
}
81+
});

src/components/hint/HintBubble.tsx

+102
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
import React from 'react';
2+
import {StyleSheet, View as RNView, LayoutChangeEvent} from 'react-native';
3+
import _ from 'lodash';
4+
5+
import {Constants} from '../../commons/new';
6+
import {BorderRadiuses, Colors, Shadows, Spacings, Typography} from 'style';
7+
import View from '../view';
8+
import Text from '../text';
9+
import Image from '../image';
10+
import {HintProps} from './types';
11+
12+
const DEFAULT_COLOR = Colors.$backgroundPrimaryHeavy;
13+
// const HINT_MIN_WIDTH = 68;
14+
15+
interface HintBubbleProps
16+
extends Pick<
17+
HintProps,
18+
| 'testID'
19+
| 'visible'
20+
| 'message'
21+
| 'messageStyle'
22+
| 'color'
23+
| 'removePaddings'
24+
| 'enableShadow'
25+
| 'borderRadius'
26+
| 'iconStyle'
27+
| 'icon'
28+
| 'customContent'
29+
> {
30+
hintRef: React.RefObject<RNView>;
31+
setHintLayout: (layoutChangeEvent: LayoutChangeEvent) => void;
32+
hintPositionStyle: {left: number};
33+
}
34+
35+
export default function HintBubble({
36+
visible,
37+
message,
38+
messageStyle,
39+
icon,
40+
iconStyle,
41+
borderRadius,
42+
removePaddings,
43+
enableShadow,
44+
color,
45+
customContent,
46+
testID,
47+
hintRef,
48+
hintPositionStyle,
49+
setHintLayout
50+
}: HintBubbleProps) {
51+
return (
52+
<View
53+
testID={`${testID}.message`}
54+
row
55+
centerV
56+
style={[
57+
styles.hint,
58+
!removePaddings && styles.hintPaddings,
59+
visible && enableShadow && styles.containerShadow,
60+
{backgroundColor: color},
61+
!_.isUndefined(borderRadius) && {borderRadius},
62+
hintPositionStyle
63+
]}
64+
onLayout={setHintLayout}
65+
ref={hintRef}
66+
>
67+
{customContent}
68+
{!customContent && icon && <Image source={icon} style={[styles.icon, iconStyle]}/>}
69+
{!customContent && (
70+
<Text recorderTag={'unmask'} style={[styles.hintMessage, messageStyle]} testID={`${testID}.message.text`}>
71+
{message}
72+
</Text>
73+
)}
74+
</View>
75+
);
76+
}
77+
78+
const styles = StyleSheet.create({
79+
hint: {
80+
// minWidth: HINT_MIN_WIDTH,
81+
maxWidth: Math.min(Constants.windowWidth - 2 * Spacings.s4, 400),
82+
borderRadius: BorderRadiuses.br60,
83+
backgroundColor: DEFAULT_COLOR
84+
},
85+
hintPaddings: {
86+
paddingHorizontal: Spacings.s5,
87+
paddingTop: Spacings.s3,
88+
paddingBottom: Spacings.s4
89+
},
90+
containerShadow: {
91+
...Shadows.sh30.bottom
92+
},
93+
hintMessage: {
94+
...Typography.text70,
95+
color: Colors.white,
96+
flexShrink: 1
97+
},
98+
icon: {
99+
marginRight: Spacings.s4,
100+
tintColor: Colors.white
101+
}
102+
});
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import React from 'react';
2+
import {StyleSheet, LayoutRectangle} from 'react-native';
3+
4+
import {Constants} from '../../commons/new';
5+
import View from '../view';
6+
import {HintProps} from './types';
7+
8+
interface HintMockChildrenProps extends Pick<HintProps, 'children' | 'backdropColor'> {
9+
targetLayout?: LayoutRectangle;
10+
}
11+
12+
export default function HintMockChildren({children, backdropColor, targetLayout}: HintMockChildrenProps) {
13+
const isBackdropColorPassed = backdropColor !== undefined;
14+
if (children && React.isValidElement(children)) {
15+
const layout = {
16+
width: targetLayout?.width,
17+
height: targetLayout?.height,
18+
right: Constants.isRTL ? targetLayout?.x : undefined,
19+
top: targetLayout?.y,
20+
left: Constants.isRTL ? undefined : targetLayout?.x
21+
};
22+
23+
return (
24+
<View style={[styles.mockChildrenContainer, layout, !isBackdropColorPassed && styles.hidden]}>
25+
{React.cloneElement<any>(children, {
26+
collapsable: false,
27+
key: 'mock',
28+
style: [children.props.style, styles.mockChildren]
29+
})}
30+
</View>
31+
);
32+
}
33+
return null;
34+
}
35+
36+
const styles = StyleSheet.create({
37+
hidden: {opacity: 0},
38+
mockChildrenContainer: {
39+
position: 'absolute'
40+
},
41+
mockChildren: {
42+
margin: undefined,
43+
marginVertical: undefined,
44+
marginHorizontal: undefined,
45+
marginTop: undefined,
46+
marginRight: undefined,
47+
marginBottom: undefined,
48+
marginLeft: undefined,
49+
50+
top: undefined,
51+
left: undefined,
52+
right: undefined,
53+
bottom: undefined
54+
}
55+
});

0 commit comments

Comments
 (0)