From 355377eaa773835d2bc0de84527a5049225faaba Mon Sep 17 00:00:00 2001 From: yashmantri Date: Wed, 16 Oct 2024 13:39:52 +0530 Subject: [PATCH 001/102] NTP-7916 | Sub Options filters support added --- src/common/Constants.ts | 8 +++ .../allCasesFilters/FilterOptions.tsx | 26 +++---- .../allCases/allCasesFilters/FilterUtils.ts | 25 +++++-- .../allCasesFilters/FiltersContainer.tsx | 5 +- .../allCases/allCasesFilters/Interface.ts | 9 +++ .../allCasesFilters/MultiSelectFilter.tsx | 70 +++++++++++++++++++ .../allCases/allCasesFilters/styles.ts | 12 ++-- .../screens/allCases/allCasesFilters/types.ts | 3 +- src/screens/allCases/Filters.tsx | 1 - 9 files changed, 133 insertions(+), 26 deletions(-) create mode 100644 src/components/screens/allCases/allCasesFilters/MultiSelectFilter.tsx diff --git a/src/common/Constants.ts b/src/common/Constants.ts index 759a84da..09d292a8 100644 --- a/src/common/Constants.ts +++ b/src/common/Constants.ts @@ -28,6 +28,14 @@ export interface Option { meta?: Record; } +export interface IFilterOption { + label: string; + value: any; + meta?: Record; + subOptions?: IFilterOption[]; + parentOption?: IFilterOption; +} + export interface SubLabelOption extends Option { subLabel?: string; } diff --git a/src/components/screens/allCases/allCasesFilters/FilterOptions.tsx b/src/components/screens/allCases/allCasesFilters/FilterOptions.tsx index 0e3dd6b8..788c1b47 100644 --- a/src/components/screens/allCases/allCasesFilters/FilterOptions.tsx +++ b/src/components/screens/allCases/allCasesFilters/FilterOptions.tsx @@ -2,14 +2,14 @@ import * as React from 'react'; import { Search } from '../../../../../RN-UI-LIB/src/utlis/search'; import { SELECTION_TYPES } from '../../../../common/Constants'; import { ScrollView, View } from 'react-native'; -import CheckboxGroup from '../../../../../RN-UI-LIB/src/components/chechbox/CheckboxGroup'; import RadioGroup from '../../../../../RN-UI-LIB/src/components/radio_button/RadioGroup'; import RadioButton from '../../../../../RN-UI-LIB/src/components/radio_button/RadioButton'; import { IFilterOptionsProps } from './Interface'; import { useSelector } from 'react-redux'; import { RootState } from '../../../../store/store'; import styles from './styles'; -import {getKeys, getOptions} from "@components/screens/allCases/allCasesFilters/FilterUtils"; +import { getKeys, getOptions } from '@components/screens/allCases/allCasesFilters/FilterUtils'; +import MultiSelectFilter from './MultiSelectFilter'; const FilterOptions: React.FC = ({ selectedFilterKey, @@ -29,8 +29,11 @@ const FilterOptions: React.FC = ({ filterSearchString.length > 0 ? Search( filterSearchString, - getOptions(filters[selectedFilterKey.filterGroup]?.filters[selectedFilterKey.filterKey].options, selectedFilterKey.filterKey) || - [], + getOptions( + filters[selectedFilterKey.filterGroup]?.filters[selectedFilterKey.filterKey].options, + selectedFilterKey.filterKey, + !!filterSearchString.length + ) || [], { keys: getKeys(selectedFilterKey.filterKey) } ).map((option) => option.obj) : getOptions(filters[selectedFilterKey.filterGroup]?.filters[selectedFilterKey.filterKey].options, selectedFilterKey.filterKey); @@ -40,15 +43,12 @@ const FilterOptions: React.FC = ({ ) { case SELECTION_TYPES.MULTIPLE: return ( - - - - - + ); case SELECTION_TYPES.SINGLE: return ( diff --git a/src/components/screens/allCases/allCasesFilters/FilterUtils.ts b/src/components/screens/allCases/allCasesFilters/FilterUtils.ts index 276ecc60..859533ac 100644 --- a/src/components/screens/allCases/allCasesFilters/FilterUtils.ts +++ b/src/components/screens/allCases/allCasesFilters/FilterUtils.ts @@ -7,14 +7,15 @@ import { } from '../../../../screens/allCases/interface'; import { CONDITIONAL_OPERATORS, - FILTER_TYPES, Option, + FILTER_TYPES, + IFilterOption, + Option, RANGE_FILTER_SEPARATOR, - SELECTION_TYPES, SubLabelOption, + SELECTION_TYPES, } from '../../../../common/Constants'; import { getObjectValueFromKeys } from '../../../utlis/parsers'; import { _map } from '../../../../../RN-UI-LIB/src/utlis/common'; -import { pluralise } from "@utils/commonFunctions"; -import { FilterKeys, OptionTypes } from "@components/screens/allCases/allCasesFilters/types"; +import { FilterKeys, OptionTypes } from '@components/screens/allCases/allCasesFilters/types'; export const evaluateFilterForCases = ( caseRecord: CaseDetail, @@ -193,11 +194,25 @@ const populateSubLabelsAndSort = (options: Option[], subLabelKey: string) => opt } }); -export const getOptions = (options: Option[], filterKey: string) => { +const flattenOptions = (options: IFilterOption[], parentOption = {} as IFilterOption) => { + return options.reduce((acc: IFilterOption[], option) => { + if (option.subOptions && option.subOptions.length > 0) { + acc = acc.concat(flattenOptions(option.subOptions, option)); // Sending option as parentOption + } else { + acc.push({ ...option, parentOption }); + } + return acc; + }, []); +}; + +export const getOptions = (options: Option[], filterKey: string, isSearchString = false) => { if (filterKey === FilterKeys.PIN_CODE) { return populateSubLabelsAndSort(options, OptionTypes.LOCALITY); } + if (filterKey === FilterKeys.FEEDBACK && isSearchString) { + return flattenOptions(options); + } return options; } diff --git a/src/components/screens/allCases/allCasesFilters/FiltersContainer.tsx b/src/components/screens/allCases/allCasesFilters/FiltersContainer.tsx index d4322f01..d7ab61a3 100644 --- a/src/components/screens/allCases/allCasesFilters/FiltersContainer.tsx +++ b/src/components/screens/allCases/allCasesFilters/FiltersContainer.tsx @@ -198,8 +198,8 @@ const FiltersContainer: React.FC = (props) => { > = (props) => { } placeholder="Search..." + value={filterSearchString} defaultValue={filterSearchString} onChangeText={handleOptionFilterChange} testID="test_search" diff --git a/src/components/screens/allCases/allCasesFilters/Interface.ts b/src/components/screens/allCases/allCasesFilters/Interface.ts index 57010d0b..4a9a3bc2 100644 --- a/src/components/screens/allCases/allCasesFilters/Interface.ts +++ b/src/components/screens/allCases/allCasesFilters/Interface.ts @@ -1,3 +1,5 @@ +import { IFilterOption } from "@common/Constants"; + export interface FilterContainerProps { closeFilterModal: () => void; isVisitPlan?: boolean; @@ -16,3 +18,10 @@ export interface IFilterOptionsProps { handleFilterSelection: (filterValues: any) => void; selectedFiltersMap: Record; } + +export interface IMultiSelectFilter { + options: IFilterOption[]; + selectedFilterKey: ISelectedFilterKey; + handleFilterSelection: (filterValues: any) => void; + selectedFiltersMap: Record; +} diff --git a/src/components/screens/allCases/allCasesFilters/MultiSelectFilter.tsx b/src/components/screens/allCases/allCasesFilters/MultiSelectFilter.tsx new file mode 100644 index 00000000..4b4294f3 --- /dev/null +++ b/src/components/screens/allCases/allCasesFilters/MultiSelectFilter.tsx @@ -0,0 +1,70 @@ +import CheckboxGroup from '@rn-ui-lib/components/chechbox/CheckboxGroup'; +import React from 'react'; +import { StyleSheet, View } from 'react-native'; +import { IMultiSelectFilter } from './Interface'; +import { ScrollView } from 'react-native'; +import { COLORS } from '@rn-ui-lib/colors'; +import { GenericStyles } from '@rn-ui-lib/styles'; + +const MultiSelectFilter = (props: IMultiSelectFilter) => { + const { options, handleFilterSelection, selectedFiltersMap, selectedFilterKey } = props; + return ( + + {options?.map((option) => ( + + + {option?.subOptions?.length + ? option.subOptions.map((subOption, index) => ( + + + + )) + : null} + + ))} + + ); +}; + +const styles = StyleSheet.create({ + subOptionContainer: { + marginLeft: 18, + paddingLeft: 2, + borderColor: COLORS.BORDER.PRIMARY, + borderLeftWidth: 1, + }, + pt20: { + paddingTop: 20, + }, + mb0: { + marginBottom: 0, + }, + pb0: { + paddingBottom: 0, + }, +}); +export default MultiSelectFilter; diff --git a/src/components/screens/allCases/allCasesFilters/styles.ts b/src/components/screens/allCases/allCasesFilters/styles.ts index bc491b24..f3790f5a 100644 --- a/src/components/screens/allCases/allCasesFilters/styles.ts +++ b/src/components/screens/allCases/allCasesFilters/styles.ts @@ -53,15 +53,15 @@ const styles = StyleSheet.create({ backgroundColor: COLORS.BACKGROUND.SILVER, borderColor: COLORS.BORDER.PRIMARY, borderWidth: 1, - height: PixelRatio.roundToNearestPixel(25), - width: PixelRatio.roundToNearestPixel(25), + height: 21, + width: 21, borderRadius: 20, alignItems: 'center', }, filterCountSelected: { backgroundColor: COLORS.TEXT.BLUE, - height: PixelRatio.roundToNearestPixel(25), - width: PixelRatio.roundToNearestPixel(25), + height: 21, + width: 21, borderRadius: 20, alignItems: 'center', }, @@ -74,6 +74,10 @@ const styles = StyleSheet.create({ paddingVertical: 8, flexShrink: 1, }, + selectedCount: { + color: COLORS.TEXT.WHITE, + lineHeight: 20, + }, }); export default styles; diff --git a/src/components/screens/allCases/allCasesFilters/types.ts b/src/components/screens/allCases/allCasesFilters/types.ts index 31bda42d..82dd9b46 100644 --- a/src/components/screens/allCases/allCasesFilters/types.ts +++ b/src/components/screens/allCases/allCasesFilters/types.ts @@ -4,5 +4,6 @@ export enum OptionTypes { } export enum FilterKeys { - PIN_CODE = 'PINCODE' + PIN_CODE = 'PINCODE', + FEEDBACK = 'FEEDBACK' } diff --git a/src/screens/allCases/Filters.tsx b/src/screens/allCases/Filters.tsx index adaf65db..f40395a2 100644 --- a/src/screens/allCases/Filters.tsx +++ b/src/screens/allCases/Filters.tsx @@ -100,7 +100,6 @@ const Filters: React.FC = ({ From 7faf744ac1ed855eb7941eaa7443bc5306d57d50 Mon Sep 17 00:00:00 2001 From: Aman Chaturvedi Date: Mon, 11 Nov 2024 18:26:15 +0530 Subject: [PATCH 002/102] NTP-7917 | copilot tour feature added --- babel.config.js | 1 + config/qa/config.js | 2 +- package.json | 8 +- src/common/ModalWrapperForAlfredV2.tsx | 2 +- .../Tour/components/AnimatedRenderMask.tsx | 113 +++++++ .../Tour/components/CopilotModal.tsx | 155 +++++++++ .../Tour/components/CopilotStep.tsx | 118 +++++++ src/components/Tour/components/Tooltip.tsx | 102 ++++++ src/components/Tour/components/style.ts | 40 +++ .../Tour/contexts/CopilotProvider.tsx | 199 ++++++++++++ .../Tour/hooks/useStateWithAwait.ts | 30 ++ src/components/Tour/hooks/useStepsMap.ts | 116 +++++++ src/components/Tour/types.ts | 114 +++++++ .../allCasesFilters/FiltersContainer.tsx | 34 +- src/constants/config.js | 2 +- yarn.lock | 298 +++++++++++++++++- 16 files changed, 1318 insertions(+), 16 deletions(-) create mode 100644 src/components/Tour/components/AnimatedRenderMask.tsx create mode 100644 src/components/Tour/components/CopilotModal.tsx create mode 100644 src/components/Tour/components/CopilotStep.tsx create mode 100644 src/components/Tour/components/Tooltip.tsx create mode 100644 src/components/Tour/components/style.ts create mode 100644 src/components/Tour/contexts/CopilotProvider.tsx create mode 100644 src/components/Tour/hooks/useStateWithAwait.ts create mode 100644 src/components/Tour/hooks/useStepsMap.ts create mode 100644 src/components/Tour/types.ts diff --git a/babel.config.js b/babel.config.js index dda01a14..74b4275c 100644 --- a/babel.config.js +++ b/babel.config.js @@ -30,6 +30,7 @@ module.exports = { }, ], 'jest-hoist', + 'react-native-reanimated/plugin' ], env: { production: { diff --git a/config/qa/config.js b/config/qa/config.js index df82b252..1e348c9a 100644 --- a/config/qa/config.js +++ b/config/qa/config.js @@ -1,6 +1,6 @@ import { MILLISECONDS_IN_A_MINUTE, MINUTES_IN_AN_HOUR } from '../../RN-UI-LIB/src/utlis/common'; -export const BASE_AV_APP_URL = 'https://qa-longhorn-portal.np.navi-tech.in/field-app'; +export const BASE_AV_APP_URL = 'https://qa-longhorn-server.np.navi-ppl.in/field-app'; export const SENTRY_DSN = 'https://acef93c884c1424cacc4ec899562e203@qa-longhorn-portal.np.navi-tech.in/glitchtip-events/173'; export const JANUS_SERVICE_URL = 'https://qa-longhorn-portal.np.navi-tech.in/api/events/json'; diff --git a/package.json b/package.json index 6972bfe1..3867cf6e 100644 --- a/package.json +++ b/package.json @@ -99,11 +99,17 @@ "react-native-webview": "13.12.1", "react-redux": "8.0.5", "redux": "4.2.0", - "redux-persist": "6.0.0" + "redux-persist": "6.0.0", + "react-native-reanimated": "3.6.3" }, "devDependencies": { "@babel/core": "7.12.9", "@babel/plugin-proposal-decorators": "7.20.7", + "@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6", + "@babel/plugin-proposal-optional-chaining": "7.21.0", + "@babel/plugin-transform-arrow-functions": "7.24.7", + "@babel/plugin-transform-shorthand-properties": "7.24.7", + "@babel/plugin-transform-template-literals": "7.24.7", "@babel/runtime": "7.12.5", "@tsconfig/react-native": "2.0.2", "@types/d3-scale": "^4.0.5", diff --git a/src/common/ModalWrapperForAlfredV2.tsx b/src/common/ModalWrapperForAlfredV2.tsx index 4577809f..6e85cc3c 100644 --- a/src/common/ModalWrapperForAlfredV2.tsx +++ b/src/common/ModalWrapperForAlfredV2.tsx @@ -34,7 +34,7 @@ const ModalWrapperForAlfredV2: React.FC = ({ children, ...props } {children} diff --git a/src/components/Tour/components/AnimatedRenderMask.tsx b/src/components/Tour/components/AnimatedRenderMask.tsx new file mode 100644 index 00000000..fd917b9e --- /dev/null +++ b/src/components/Tour/components/AnimatedRenderMask.tsx @@ -0,0 +1,113 @@ +import React, { useEffect } from 'react'; +import { View } from 'react-native'; +import Svg, { Circle, Defs, Mask, Rect, G } from 'react-native-svg'; +import Animated, { + Easing, + interpolate, + useAnimatedProps, + useSharedValue, + withRepeat, + withTiming, +} from 'react-native-reanimated'; +import { SCREEN_WIDTH } from '@rn-ui-lib/styles'; +import { useCopilot } from '../contexts/CopilotProvider'; +import { IAnimatedRenderMask } from '../types'; + +const HIGHLIGHT_PADDING = 2; + +const AnimatedCircle = Animated.createAnimatedComponent(Circle); +const AnimatedRect = Animated.createAnimatedComponent(Rect); + +const AnimatedRenderMask: React.FC = ({ maskRect }) => { + const circleCenterX = useSharedValue(0); + const circleCenterY = useSharedValue(0); + const rippleAnimation = useSharedValue(0); + + const highlightAreaX = maskRect.x - 2 * HIGHLIGHT_PADDING; + const highlightAreaY = maskRect.y - HIGHLIGHT_PADDING; + const highlightAreaWidth = maskRect.width + 4 * HIGHLIGHT_PADDING; + const highlightAreaHeight = maskRect.height + 2 * HIGHLIGHT_PADDING; + const highlightAreaRadius = (maskRect.height + HIGHLIGHT_PADDING) / 2; + + useEffect(() => { + circleCenterX.value = maskRect.x + maskRect.width / 2; + circleCenterY.value = maskRect.y + maskRect.height / 2; + }, [maskRect]); + + useEffect(() => { + rippleAnimation.value = withRepeat( + withTiming(1, { duration: 500, easing: Easing.inOut(Easing.ease) }), + -1, + true + ); + }, []); + + const circleAnimatedProps = useAnimatedProps(() => ({ + transform: [ + { + translateX: withTiming(circleCenterX.value, { + duration: 200, + }), + }, + { + translateY: withTiming(circleCenterY.value, { + duration: 200, + }), + }, + ], + })); + + // const animatedRectProps = useAnimatedProps(() => { + // const animatedExpansion = interpolate(rippleAnimation.value, [0, 1], [0, 10]); + // return { + // // x: highlightAreaX - animatedExpansion, + // // y: highlightAreaY - animatedExpansion, + // // width: highlightAreaWidth + animatedExpansion * 2, + // // height: highlightAreaHeight + animatedExpansion * 2, + // // rx: highlightAreaRadius + animatedExpansion, + // strokeWidth: animatedExpansion, + // // strokeOpacity: interpolate(rippleAnimation.value, [0, 1], [0.7, 0.2]), + // }; + // }); + + return ( + + + + + + + + + + + + {/* */} + + + ); +}; + +export default React.memo(AnimatedRenderMask); diff --git a/src/components/Tour/components/CopilotModal.tsx b/src/components/Tour/components/CopilotModal.tsx new file mode 100644 index 00000000..86d767e0 --- /dev/null +++ b/src/components/Tour/components/CopilotModal.tsx @@ -0,0 +1,155 @@ +import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useState } from 'react'; +import { View, LayoutRectangle, ViewStyle } from 'react-native'; +import { useCopilot } from '../contexts/CopilotProvider'; +import { CopilotModalHandle, CopilotOptions } from '../types'; +import { MARGIN, styles } from './style'; +import AnimatedRenderMask from './AnimatedRenderMask'; +import { Tooltip } from './Tooltip'; +import { SCREEN_HEIGHT, SCREEN_WIDTH } from '@rn-ui-lib/styles'; +import ModalWrapperForAlfredV2 from '@common/ModalWrapperForAlfredV2'; + +export const CopilotModal = forwardRef(function CopilotModal( + { + tooltipComponent: TooltipComponent = Tooltip, + tooltipStyle = {}, + labels = { + finish: 'Got it', + next: 'Next', + previous: 'Previous', + skip: 'Skip', + }, + margin = MARGIN, + }, + ref +) { + const { currentStep, visible } = useCopilot(); + const [tooltipStyles, setTooltipStyles] = useState({}); + const [maskRect, setMaskRect] = useState(); + const { noHighlightArea } = currentStep || {}; + + const [containerVisible, setContainerVisible] = useState(false); + + useEffect(() => { + if (visible) { + setContainerVisible(true); + } else { + reset(); + } + }, [visible]); + + const setStylesForNoHighlightArea = (rect: LayoutRectangle) => { + setMaskRect({ + width: 0, + height: 0, + x: SCREEN_WIDTH / 2, + y: SCREEN_HEIGHT + SCREEN_WIDTH / 2, + }); + + setTooltipStyles({ + bottom: SCREEN_WIDTH / 8, + flexDirection: 'row', + justifyContent: 'center', + alignItems: 'center', + width: SCREEN_WIDTH, + }); + }; + + const _animateMove = useCallback( + async (rect: LayoutRectangle) => { + if (noHighlightArea) { + setStylesForNoHighlightArea(rect); + return; + } + const center = { + x: rect.x + rect.width / 2, + y: rect.y + rect.height / 2, + }; + + const relativeToLeft = center.x; + const relativeToTop = center.y; + const relativeToBottom = Math.abs(center.y - SCREEN_HEIGHT); + const relativeToRight = Math.abs(center.x - SCREEN_WIDTH); + + const verticalPosition = relativeToBottom > relativeToTop ? 'bottom' : 'top'; + const horizontalPosition = relativeToLeft > relativeToRight ? 'left' : 'right'; + + const tooltip: ViewStyle = {}; + + if (verticalPosition === 'bottom') { + tooltip.top = rect.y + rect.height + margin; + } else { + tooltip.bottom = noHighlightArea ? 40 : SCREEN_HEIGHT - rect.y + margin + margin; + } + + if (horizontalPosition === 'left') { + tooltip.right = Math.min(SCREEN_WIDTH - (rect.x + rect.width), 24); + } else { + tooltip.left = noHighlightArea ? 50 : Math.min(rect.x, 24); + } + + setTooltipStyles(tooltip); + setMaskRect({ + width: rect.width, + height: rect.height, + x: Math.floor(Math.max(rect.x, 0)), + y: Math.floor(Math.max(rect.y, 0)), + }); + }, + [margin, currentStep] + ); + + const animateMove = useCallback( + async (rect) => { + await new Promise((resolve) => { + const frame = async () => { + await _animateMove(rect); + resolve(); + }; + + setContainerVisible(true); + requestAnimationFrame(() => { + frame(); + }); + }); + }, + [_animateMove] + ); + + const reset = () => { + setContainerVisible(false); + setMaskRect(undefined); + }; + + useImperativeHandle( + ref, + () => { + return { + animateMove, + }; + }, + [animateMove] + ); + + const modalVisible = containerVisible || visible; + + if (!modalVisible) { + return null; + } + + return ( + + + {containerVisible && maskRect ? : null} + {containerVisible && maskRect ? ( + + + + ) : null} + + + ); +}); diff --git a/src/components/Tour/components/CopilotStep.tsx b/src/components/Tour/components/CopilotStep.tsx new file mode 100644 index 00000000..53ae5f4b --- /dev/null +++ b/src/components/Tour/components/CopilotStep.tsx @@ -0,0 +1,118 @@ +import React, { useEffect, useMemo, useRef } from 'react'; +import { View, type NativeMethods } from 'react-native'; +import { useCopilot } from '../contexts/CopilotProvider'; +import { GenericStyles, SCREEN_HEIGHT, SCREEN_WIDTH } from '@rn-ui-lib/styles'; +import { CopilotStepProps } from '../types'; + +export const CopilotStep = ({ + name, + order, + text, + children, + active = true, + width, + height, + isCircularHighlight, + noHighlightArea, +}: CopilotStepProps) => { + const registeredName = useRef(null); + const { registerStep, unregisterStep, currentStepNumber, visible } = useCopilot(); + const wrapperRef = React.useRef(null); + + const measure = async () => { + return await new Promise<{ + x: number; + y: number; + width: number; + height: number; + }>((resolve) => { + const measure = () => { + if (noHighlightArea) { + resolve({ + x: 0, + y: 0, + width: 0, + height: 0, + }); + return; + } + // Wait until the wrapper element appears + if (wrapperRef.current != null && 'measure' in wrapperRef.current) { + wrapperRef.current.measure((_ox, _oy, elementWidth, elementHeight, x, y) => { + resolve({ + x, + y, + width: width || elementWidth, + height: height || elementHeight, + }); + }); + } else { + requestAnimationFrame(measure); + } + }; + + measure(); + }); + }; + + useEffect(() => { + if (active) { + if (registeredName.current && registeredName.current !== name) { + unregisterStep(registeredName.current); + } + registerStep({ + name, + text, + order, + measure, + wrapperRef, + visible: true, + isCircularHighlight, + noHighlightArea, + }); + registeredName.current = name; + } + }, [active]); + + useEffect(() => { + if (active) { + return () => { + if (registeredName.current) { + unregisterStep(registeredName.current); + } + }; + } + }, [name, unregisterStep, active]); + + const copilotProps = useMemo( + () => ({ + onLayout: () => {}, + }), + [] + ); + + const isNoHighlightAreaStepActive = visible && noHighlightArea && currentStepNumber === order; + + if (noHighlightArea) { + if (!isNoHighlightAreaStepActive) { + return null; + } + return ( + + {children} + + ); + } + + return React.cloneElement(children, { ...copilotProps, ref: wrapperRef }); +}; diff --git a/src/components/Tour/components/Tooltip.tsx b/src/components/Tour/components/Tooltip.tsx new file mode 100644 index 00000000..a399887b --- /dev/null +++ b/src/components/Tour/components/Tooltip.tsx @@ -0,0 +1,102 @@ +import React, { useEffect } from 'react'; +import { View } from 'react-native'; +import Button from '@rn-ui-lib/components/Button'; +import Text from '@rn-ui-lib/components/Text'; +import { GenericStyles } from '@rn-ui-lib/styles'; +import { useCopilot } from '../contexts/CopilotProvider'; +import { styles } from './style'; +import { ITooltip } from '../types'; +import Animated, { useAnimatedStyle, useSharedValue, withTiming } from 'react-native-reanimated'; + +export const Tooltip: React.FC = ({ labels }) => { + const { + goToNext, + goToPrev, + stop, + currentStep, + isLastStep, + isFirstStep, + currentStepNumber, + totalStepsNumber, + } = useCopilot(); + const tooltipOpacity = useSharedValue(0); + + useEffect(() => { + if (currentStepNumber) { + tooltipOpacity.value = 0; + tooltipOpacity.value = withTiming(1, { duration: 1200 }); + } + }, [currentStepNumber]); + + const animatedStyle = useAnimatedStyle(() => { + return { + opacity: tooltipOpacity.value, + }; + }); + + const handleStop = () => { + stop(); + }; + const handleNext = () => { + if (isLastStep) { + handleStop(); + return; + } + goToNext(); + }; + + const handlePrevious = () => { + goToPrev(); + }; + + return ( + + + {currentStep?.text} + + + {totalStepsNumber > 1 ? ( + + {currentStepNumber}/{totalStepsNumber} + + ) : null} + +