diff --git a/example/package.json b/example/package.json
index 983ed7a..f6e60be 100644
--- a/example/package.json
+++ b/example/package.json
@@ -15,6 +15,8 @@
"react": "18.3.1",
"react-dom": "18.3.1",
"react-native": "0.76.6",
+ "react-native-reanimated": "^3.16.7",
+ "react-native-svg": "^15.11.1",
"react-native-web": "~0.19.13"
},
"devDependencies": {
diff --git a/example/src/App.tsx b/example/src/App.tsx
index 6403632..17c9b0c 100644
--- a/example/src/App.tsx
+++ b/example/src/App.tsx
@@ -1,12 +1,65 @@
-import { Text, View, StyleSheet } from 'react-native';
-import { multiply } from 'react-native-tipkit';
-
-const result = multiply({ n1: 3, n2: 7 });
+import { View, StyleSheet, Pressable, Text } from 'react-native';
+import { TipKitInlineView, TipKitPopOverView } from 'react-native-tipkit';
+import CloseIcon from './CloseIcon';
export default function App() {
+ const onActionButtonPress = () => {
+ console.log('Action button pressed');
+ };
+
return (
- Result: {result}
+ TipKit Example
+
+
+ }
+ actionButtonOnPress={onActionButtonPress}
+ actionButtonTitle="Learn more"
+ // Popover Button Props
+ popoverButtonProps={{ title: 'Show Popover Top' }}
+ popoverButtonArrowDirection="top"
+ />
+
+ }
+ actionButtonOnPress={onActionButtonPress}
+ // Popover Button Props
+ popoverButtonProps={{ title: 'Show Popover Bottom' }}
+ popoverButtonArrowDirection="bottom"
+ />
+
+ {/* TODO: Fix the positioning of the arrow when the button is smaller than 100% */}
+ }
+ actionButtonOnPress={onActionButtonPress}
+ popoverButtonArrowDirection="bottom-end"
+ // Popover Button Props
+ popoverButton={
+ {}}>
+
+ Custom Popover button
+
+
+ }
+ />
);
}
@@ -14,7 +67,31 @@ export default function App() {
const styles = StyleSheet.create({
container: {
flex: 1,
- alignItems: 'center',
+ gap: 12,
justifyContent: 'center',
+ paddingHorizontal: 12,
+ },
+ inline: {
+ backgroundColor: '#d7eef3',
+ },
+ tipContainer: {
+ backgroundColor: '#f1f4f2',
+ },
+ customPopoverButton: {
+ width: '60%',
+ backgroundColor: '#66D210',
+ padding: 12,
+ borderRadius: 8,
+ },
+ customPopoverButtonText: {
+ color: 'white',
+ textAlign: 'center',
+ fontWeight: 'bold',
+ },
+ title: {
+ fontSize: 24,
+ fontWeight: 'bold',
+ textAlign: 'center',
+ color: '#333',
},
});
diff --git a/example/src/CloseIcon.tsx b/example/src/CloseIcon.tsx
new file mode 100644
index 0000000..1f160d7
--- /dev/null
+++ b/example/src/CloseIcon.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import Svg, { Path, type SvgProps } from 'react-native-svg';
+
+type CloseIconProps = SvgProps;
+
+const CloseIcon: React.FC = ({ ...props }) => {
+ return (
+
+ );
+};
+
+export default CloseIcon;
diff --git a/package.json b/package.json
index aaa8f66..a4df950 100644
--- a/package.json
+++ b/package.json
@@ -80,6 +80,8 @@
"react": "18.3.1",
"react-native": "0.76.6",
"react-native-builder-bob": "^0.32.0",
+ "react-native-reanimated": "^3.16.7",
+ "react-native-svg": "^15.11.1",
"release-it": "^17.10.0",
"typescript": "^5.2.2"
},
@@ -88,7 +90,9 @@
},
"peerDependencies": {
"react": "*",
- "react-native": "*"
+ "react-native": "*",
+ "react-native-reanimated": "*",
+ "react-native-svg": "*"
},
"workspaces": [
"example"
@@ -196,8 +200,5 @@
"languages": "js",
"type": "library",
"version": "0.45.5"
- },
- "dependencies": {
- "react-native-reanimated": "^3.16.7"
}
}
diff --git a/src/TipKitInlineView/TipKitInlineView.tsx b/src/TipKitInlineView/TipKitInlineView.tsx
new file mode 100644
index 0000000..bf11bdc
--- /dev/null
+++ b/src/TipKitInlineView/TipKitInlineView.tsx
@@ -0,0 +1,10 @@
+import React from 'react';
+import BaseTipKit, { type BaseTipKitProps } from '../components/BaseTipKit';
+
+interface TipKitInlineViewProps extends BaseTipKitProps {}
+
+const TipKitInlineView: React.FC = ({ ...rest }) => {
+ return ;
+};
+
+export default TipKitInlineView;
diff --git a/src/TipKitInlineView/index.tsx b/src/TipKitInlineView/index.tsx
new file mode 100644
index 0000000..04f47f7
--- /dev/null
+++ b/src/TipKitInlineView/index.tsx
@@ -0,0 +1,2 @@
+import TipKitInlineView from './TipKitInlineView';
+export { TipKitInlineView };
diff --git a/src/TipKitPopOverView/TipKitPopOverView.tsx b/src/TipKitPopOverView/TipKitPopOverView.tsx
new file mode 100644
index 0000000..7616ac9
--- /dev/null
+++ b/src/TipKitPopOverView/TipKitPopOverView.tsx
@@ -0,0 +1,115 @@
+import React, {
+ Fragment,
+ useMemo,
+ useRef,
+ useState,
+ type JSXElementConstructor,
+ type ReactElement,
+} from 'react';
+import BaseTipKit, { type BaseTipKitProps } from '../components/BaseTipKit';
+import { Button, StyleSheet, View, type ButtonProps } from 'react-native';
+
+export type TipKitPopOverArrowDirection =
+ | 'top-start'
+ | 'top'
+ | 'top-end'
+ | 'bottom-start'
+ | 'bottom'
+ | 'bottom-end';
+
+interface TipKitPopOverViewProps extends BaseTipKitProps {
+ popoverButtonArrowDirection?: TipKitPopOverArrowDirection;
+ popoverButton?: ReactElement>;
+ popoverButtonOnPress?: () => void;
+ popoverButtonTitle?: string;
+ popoverButtonProps?: ButtonProps;
+}
+
+const TipKitPopOverView: React.FC = ({
+ popoverButton,
+ popoverButtonOnPress,
+ popoverButtonProps,
+ popoverButtonArrowDirection = 'bottom',
+ ...rest
+}) => {
+ const buttonRef = useRef(null);
+
+ const [visible, setVisible] = useState(false);
+ const [buttonPosition, setButtonPosition] = useState({
+ x: 0,
+ y: 0,
+ width: 0,
+ height: 0,
+ });
+
+ const measureButtonPosition = () => {
+ buttonRef.current?.measure((_fx, _fy, width, height, px, py) => {
+ setButtonPosition({ x: px, y: py, width, height });
+ });
+ };
+
+ const popoverStyle = useMemo(() => {
+ const { y, x, height } = buttonPosition;
+ const isTop = popoverButtonArrowDirection?.includes('top');
+
+ return {
+ top: y + (isTop ? -height * 2.5 : height * 1.5),
+ left: x,
+ };
+ }, [buttonPosition, popoverButtonArrowDirection]);
+
+ const onDismiss = () => {
+ setVisible(false);
+ };
+
+ const handlePopoverButtonPress = () => {
+ measureButtonPosition();
+ popoverButtonOnPress?.();
+ setVisible(true);
+ };
+
+ return (
+
+
+ {popoverButton ? (
+ React.cloneElement(popoverButton, {
+ onPress: () => {
+ handlePopoverButtonPress();
+ popoverButton.props.onPress?.();
+ },
+ })
+ ) : (
+
+ )}
+
+ {visible && (
+
+
+
+ )}
+
+ );
+};
+
+const styles = StyleSheet.create({
+ buttonContainer: {
+ zIndex: 9998,
+ },
+ popoverContainer: {
+ position: 'absolute',
+ zIndex: 9999,
+ width: '100%',
+ },
+});
+
+export default TipKitPopOverView;
diff --git a/src/TipKitPopOverView/index.tsx b/src/TipKitPopOverView/index.tsx
new file mode 100644
index 0000000..aa564e9
--- /dev/null
+++ b/src/TipKitPopOverView/index.tsx
@@ -0,0 +1,2 @@
+import TipKitPopOverView from './TipKitPopOverView';
+export { TipKitPopOverView };
diff --git a/src/components/BaseTipKit.tsx b/src/components/BaseTipKit.tsx
new file mode 100644
index 0000000..496e06f
--- /dev/null
+++ b/src/components/BaseTipKit.tsx
@@ -0,0 +1,164 @@
+import React, { useMemo, type FC } from 'react';
+import {
+ Pressable,
+ StyleSheet,
+ Text,
+ View,
+ type FlexAlignType,
+ type TextStyle,
+ type ViewStyle,
+} from 'react-native';
+import CloseIcon from './CloseIcon';
+import type { TipKitPopOverArrowDirection } from '../TipKitPopOverView/TipKitPopOverView';
+
+export interface BaseTipKitProps {
+ // General Logic Props
+ visible?: boolean;
+ onDismiss?: () => void;
+
+ // Content Props
+ title?: string;
+ titleStyle?: TextStyle;
+ description?: string;
+ descriptionStyle?: TextStyle;
+ popoverButtonArrowDirection?: TipKitPopOverArrowDirection;
+
+ // Icon Props
+ leftIcon?: React.ReactNode;
+
+ // Button Props
+ actionButtonTitle?: string;
+ actionButtonStyle?: TextStyle;
+ actionButtonOnPress?: () => void;
+
+ // Styling Props
+ tipContainer?: ViewStyle;
+}
+
+const BaseTipKit: FC = ({
+ visible,
+ onDismiss,
+ title,
+ titleStyle,
+ description,
+ descriptionStyle,
+ leftIcon,
+ actionButtonTitle,
+ actionButtonStyle,
+ actionButtonOnPress,
+ tipContainer,
+ popoverButtonArrowDirection,
+}) => {
+ const onXPress = () => {
+ onDismiss?.();
+ };
+
+ const arrowStyle = useMemo(() => {
+ const isTop = popoverButtonArrowDirection?.includes('top');
+ const isStart = popoverButtonArrowDirection?.includes('start');
+ const alignSelf = (() => {
+ switch (popoverButtonArrowDirection) {
+ case 'top-start':
+ case 'bottom-start':
+ return 'flex-start';
+ case 'top-end':
+ case 'bottom-end':
+ return 'flex-end';
+ case 'top':
+ case 'bottom':
+ return 'center';
+ default:
+ return 'center';
+ }
+ })();
+
+ return {
+ top: isTop ? undefined : -7,
+ bottom: isTop ? -7 : undefined,
+ left: isStart ? 6 : undefined,
+ right: isStart ? undefined : 6,
+ alignSelf: alignSelf as FlexAlignType,
+ backgroundColor: tipContainer?.backgroundColor,
+ };
+ }, [popoverButtonArrowDirection, tipContainer?.backgroundColor]);
+
+ return (
+ visible && (
+
+ {popoverButtonArrowDirection && (
+
+ )}
+
+ {leftIcon && leftIcon}
+
+
+ {title}
+
+
+
+
+
+ {description}
+
+
+ {actionButtonOnPress && actionButtonTitle && (
+
+
+ {actionButtonTitle}
+
+
+ )}
+
+
+
+ )
+ );
+};
+
+const styles = StyleSheet.create({
+ container: {
+ paddingHorizontal: 12,
+ paddingVertical: 14,
+ borderRadius: 8,
+ backgroundColor: '#f1f4f2',
+ flexDirection: 'row',
+ gap: 10,
+ },
+ wrapper: {
+ flex: 1,
+ flexShrink: 1,
+ },
+ header: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ },
+ title: {
+ fontSize: 16,
+ fontWeight: '600',
+ color: '#030607',
+ },
+ description: {
+ fontSize: 14,
+ color: '#6D6D72',
+ },
+ actionButton: {
+ borderTopColor: '#D1D1D6',
+ borderTopWidth: 1,
+ marginTop: 8,
+ paddingTop: 10,
+
+ fontSize: 16,
+ color: '#007AFF',
+ },
+ arrow: {
+ height: 20,
+ width: 20,
+ position: 'absolute',
+ top: -7,
+ borderBottomLeftRadius: 4,
+ borderTopRightRadius: 4,
+ transform: [{ rotate: '45deg' }],
+ },
+});
+
+export default BaseTipKit;
diff --git a/src/components/CloseIcon.tsx b/src/components/CloseIcon.tsx
new file mode 100644
index 0000000..1f160d7
--- /dev/null
+++ b/src/components/CloseIcon.tsx
@@ -0,0 +1,14 @@
+import React from 'react';
+import Svg, { Path, type SvgProps } from 'react-native-svg';
+
+type CloseIconProps = SvgProps;
+
+const CloseIcon: React.FC = ({ ...props }) => {
+ return (
+
+ );
+};
+
+export default CloseIcon;
diff --git a/src/index.tsx b/src/index.tsx
index 82a516e..f4a88e5 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,9 +1,4 @@
-import { View } from 'react-native';
+import { TipKitInlineView } from './TipKitInlineView';
+import { TipKitPopOverView } from './TipKitPopOverView';
-export function multiply({ n1, n2 }: { n1: number; n2: number }): number {
- return n1 * n2;
-}
-
-export const Test = () => {
- return ;
-};
+export { TipKitInlineView, TipKitPopOverView };
diff --git a/yarn.lock b/yarn.lock
index aeeafe5..ab9693f 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4271,6 +4271,13 @@ __metadata:
languageName: node
linkType: hard
+"boolbase@npm:^1.0.0":
+ version: 1.0.0
+ resolution: "boolbase@npm:1.0.0"
+ checksum: 3e25c80ef626c3a3487c73dbfc70ac322ec830666c9ad915d11b701142fab25ec1e63eff2c450c74347acfd2de854ccde865cd79ef4db1683f7c7b046ea43bb0
+ languageName: node
+ linkType: hard
+
"boxen@npm:^8.0.1":
version: 8.0.1
resolution: "boxen@npm:8.0.1"
@@ -5346,6 +5353,36 @@ __metadata:
languageName: node
linkType: hard
+"css-select@npm:^5.1.0":
+ version: 5.1.0
+ resolution: "css-select@npm:5.1.0"
+ dependencies:
+ boolbase: ^1.0.0
+ css-what: ^6.1.0
+ domhandler: ^5.0.2
+ domutils: ^3.0.1
+ nth-check: ^2.0.1
+ checksum: 2772c049b188d3b8a8159907192e926e11824aea525b8282981f72ba3f349cf9ecd523fdf7734875ee2cb772246c22117fc062da105b6d59afe8dcd5c99c9bda
+ languageName: node
+ linkType: hard
+
+"css-tree@npm:^1.1.3":
+ version: 1.1.3
+ resolution: "css-tree@npm:1.1.3"
+ dependencies:
+ mdn-data: 2.0.14
+ source-map: ^0.6.1
+ checksum: 79f9b81803991b6977b7fcb1588799270438274d89066ce08f117f5cdb5e20019b446d766c61506dd772c839df84caa16042d6076f20c97187f5abe3b50e7d1f
+ languageName: node
+ linkType: hard
+
+"css-what@npm:^6.1.0":
+ version: 6.1.0
+ resolution: "css-what@npm:6.1.0"
+ checksum: b975e547e1e90b79625918f84e67db5d33d896e6de846c9b584094e529f0c63e2ab85ee33b9daffd05bff3a146a1916bec664e18bb76dd5f66cbff9fc13b2bbe
+ languageName: node
+ linkType: hard
+
"csstype@npm:^3.0.2":
version: 3.1.3
resolution: "csstype@npm:3.1.3"
@@ -5714,6 +5751,44 @@ __metadata:
languageName: node
linkType: hard
+"dom-serializer@npm:^2.0.0":
+ version: 2.0.0
+ resolution: "dom-serializer@npm:2.0.0"
+ dependencies:
+ domelementtype: ^2.3.0
+ domhandler: ^5.0.2
+ entities: ^4.2.0
+ checksum: cd1810544fd8cdfbd51fa2c0c1128ec3a13ba92f14e61b7650b5de421b88205fd2e3f0cc6ace82f13334114addb90ed1c2f23074a51770a8e9c1273acbc7f3e6
+ languageName: node
+ linkType: hard
+
+"domelementtype@npm:^2.3.0":
+ version: 2.3.0
+ resolution: "domelementtype@npm:2.3.0"
+ checksum: ee837a318ff702622f383409d1f5b25dd1024b692ef64d3096ff702e26339f8e345820f29a68bcdcea8cfee3531776b3382651232fbeae95612d6f0a75efb4f6
+ languageName: node
+ linkType: hard
+
+"domhandler@npm:^5.0.2, domhandler@npm:^5.0.3":
+ version: 5.0.3
+ resolution: "domhandler@npm:5.0.3"
+ dependencies:
+ domelementtype: ^2.3.0
+ checksum: 0f58f4a6af63e6f3a4320aa446d28b5790a009018707bce2859dcb1d21144c7876482b5188395a188dfa974238c019e0a1e610d2fc269a12b2c192ea2b0b131c
+ languageName: node
+ linkType: hard
+
+"domutils@npm:^3.0.1":
+ version: 3.2.2
+ resolution: "domutils@npm:3.2.2"
+ dependencies:
+ dom-serializer: ^2.0.0
+ domelementtype: ^2.3.0
+ domhandler: ^5.0.3
+ checksum: ae941d56f03d857077d55dde9297e960a625229fc2b933187cc4123084d7c2d2517f58283a7336567127029f1e008449bac8ac8506d44341e29e3bb18e02f906
+ languageName: node
+ linkType: hard
+
"dot-prop@npm:^5.1.0":
version: 5.3.0
resolution: "dot-prop@npm:5.3.0"
@@ -5840,6 +5915,13 @@ __metadata:
languageName: node
linkType: hard
+"entities@npm:^4.2.0":
+ version: 4.5.0
+ resolution: "entities@npm:4.5.0"
+ checksum: 853f8ebd5b425d350bffa97dd6958143179a5938352ccae092c62d1267c4e392a039be1bae7d51b6e4ffad25f51f9617531fedf5237f15df302ccfb452cbf2d7
+ languageName: node
+ linkType: hard
+
"env-editor@npm:^0.4.1":
version: 0.4.2
resolution: "env-editor@npm:0.4.2"
@@ -9715,6 +9797,13 @@ __metadata:
languageName: node
linkType: hard
+"mdn-data@npm:2.0.14":
+ version: 2.0.14
+ resolution: "mdn-data@npm:2.0.14"
+ checksum: 9d0128ed425a89f4cba8f787dca27ad9408b5cb1b220af2d938e2a0629d17d879a34d2cb19318bdb26c3f14c77dd5dfbae67211f5caaf07b61b1f2c5c8c7dc16
+ languageName: node
+ linkType: hard
+
"memoize-one@npm:^5.0.0":
version: 5.2.1
resolution: "memoize-one@npm:5.2.1"
@@ -10791,6 +10880,15 @@ __metadata:
languageName: node
linkType: hard
+"nth-check@npm:^2.0.1":
+ version: 2.1.1
+ resolution: "nth-check@npm:2.1.1"
+ dependencies:
+ boolbase: ^1.0.0
+ checksum: 5afc3dafcd1573b08877ca8e6148c52abd565f1d06b1eb08caf982e3fa289a82f2cae697ffb55b5021e146d60443f1590a5d6b944844e944714a5b549675bcd3
+ languageName: node
+ linkType: hard
+
"nullthrows@npm:^1.1.1":
version: 1.1.1
resolution: "nullthrows@npm:1.1.1"
@@ -11813,6 +11911,20 @@ __metadata:
languageName: node
linkType: hard
+"react-native-svg@npm:^15.11.1":
+ version: 15.11.1
+ resolution: "react-native-svg@npm:15.11.1"
+ dependencies:
+ css-select: ^5.1.0
+ css-tree: ^1.1.3
+ warn-once: 0.1.1
+ peerDependencies:
+ react: "*"
+ react-native: "*"
+ checksum: be3194d2f3255b90bb239f045092ea46f677277fbd3e02ed6c79a5bcddae487081c971a1f0ed095625806f688e5b3fd5aed6ac200061057fe2bda0e7f09933f6
+ languageName: node
+ linkType: hard
+
"react-native-tipkit-example@workspace:example":
version: 0.0.0-use.local
resolution: "react-native-tipkit-example@workspace:example"
@@ -11825,6 +11937,8 @@ __metadata:
react-dom: 18.3.1
react-native: 0.76.6
react-native-builder-bob: ^0.32.0
+ react-native-reanimated: ^3.16.7
+ react-native-svg: ^15.11.1
react-native-web: ~0.19.13
languageName: unknown
linkType: soft
@@ -11850,11 +11964,14 @@ __metadata:
react-native: 0.76.6
react-native-builder-bob: ^0.32.0
react-native-reanimated: ^3.16.7
+ react-native-svg: ^15.11.1
release-it: ^17.10.0
typescript: ^5.2.2
peerDependencies:
react: "*"
react-native: "*"
+ react-native-reanimated: "*"
+ react-native-svg: "*"
languageName: unknown
linkType: soft
@@ -14221,6 +14338,13 @@ __metadata:
languageName: node
linkType: hard
+"warn-once@npm:0.1.1":
+ version: 0.1.1
+ resolution: "warn-once@npm:0.1.1"
+ checksum: e6a5a1f5a8dba7744399743d3cfb571db4c3947897875d4962a7c5b1bf2195ab4518c838cb4cea652e71729f21bba2e98dc75686f5fccde0fabbd894e2ed0c0d
+ languageName: node
+ linkType: hard
+
"wcwidth@npm:^1.0.1":
version: 1.0.1
resolution: "wcwidth@npm:1.0.1"