@hero-design/rn 8.132.1 → 8.133.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/lib/index.js CHANGED
@@ -5,9 +5,9 @@ var React = require('react');
5
5
  var MaskedView = require('@react-native-masked-view/masked-view');
6
6
  var expoLinearGradient = require('expo-linear-gradient');
7
7
  var reactNativeVectorIcons = require('react-native-vector-icons');
8
+ var Svg = require('react-native-svg');
8
9
  var reactNativeSafeAreaContext = require('react-native-safe-area-context');
9
10
  var reactNativeMonthYearPicker = require('@hero-design/react-native-month-year-picker');
10
- var Svg = require('react-native-svg');
11
11
  var DateTimePicker = require('@react-native-community/datetimepicker');
12
12
  var RnSlider = require('@react-native-community/slider');
13
13
  var MultiSlider = require('@ptomasroos/react-native-multi-slider');
@@ -7797,7 +7797,8 @@ var getAppCueTheme = function getAppCueTheme(theme) {
7797
7797
  arrowOffset: theme.space.smallMedium
7798
7798
  };
7799
7799
  var radii = {
7800
- container: theme.radii.large
7800
+ container: theme.radii.large,
7801
+ spotlight: theme.radii['5xlarge']
7801
7802
  };
7802
7803
  return {
7803
7804
  colors: colors,
@@ -9494,17 +9495,16 @@ var StyledContent$1 = index$c(reactNative.View)(function (_ref) {
9494
9495
  alignSelf: 'center'
9495
9496
  };
9496
9497
  });
9497
- var StyledContainer$a = index$c(reactNative.View)(function (_ref2) {
9498
- var theme = _ref2.theme;
9499
- return {
9500
- width: '100%',
9501
- height: '100%',
9502
- backgroundColor: theme.__hd__.appCue.colors.backdropColor
9503
- };
9498
+ var StyledModalContainer = index$c(reactNative.View)({
9499
+ width: '100%',
9500
+ height: '100%'
9504
9501
  });
9505
- var StyledIconContainer$2 = index$c(reactNative.Animated.View)(function (_ref3) {
9506
- var theme = _ref3.theme,
9507
- themePlacement = _ref3.themePlacement;
9502
+ var StyledContentPositioner = index$c(reactNative.View)({
9503
+ position: 'absolute'
9504
+ });
9505
+ var StyledIconContainer$2 = index$c(reactNative.Animated.View)(function (_ref2) {
9506
+ var theme = _ref2.theme,
9507
+ themePlacement = _ref2.themePlacement;
9508
9508
  return _objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2(_objectSpread2({
9509
9509
  position: 'absolute',
9510
9510
  color: theme.__hd__.appCue.colors.background,
@@ -9553,25 +9553,47 @@ var StyledIconContainer$2 = index$c(reactNative.Animated.View)(function (_ref3)
9553
9553
  });
9554
9554
  });
9555
9555
 
9556
- /**
9557
- * Calculates the position of an element based on its placement relative to a reference position.
9558
- *
9559
- * @param {Object} params - The parameters for calculating the position.
9560
- * @param {Object} params.position - The position and size of target element.
9561
- * @param {number} params.position.pageX - The X coordinate of the target position.
9562
- * @param {number} params.position.pageY - The Y coordinate of the target position.
9563
- * @param {number} params.position.width - The width of the reference element.
9564
- * @param {number} params.position.height - The height of the reference element.
9565
- * @param {Object} params.contentSize - The size of the App Cue content.
9566
- * @param {number} params.contentSize.width - The width of the content.
9567
- * @param {number} params.contentSize.height - The height of the content.
9568
- * @param {Placement} params.placement - The placement of the content relative to the reference position ('top', 'bottom', 'right', 'left').
9569
- * @param {number} params.offset - The offset distance to display an arrow.
9570
- *
9571
- * @returns {Object} The calculated position of the App Cue.
9572
- * @returns {number} return.x - The X coordinate of the App Cue.
9573
- * @returns {number} return.y - The Y coordinate of the App Cue.
9574
- */
9556
+ var SpotlightBackdrop = function SpotlightBackdrop(_ref) {
9557
+ var bounds = _ref.bounds,
9558
+ testID = _ref.testID;
9559
+ var theme = useTheme();
9560
+ var _useWindowDimensions = reactNative.useWindowDimensions(),
9561
+ windowWidth = _useWindowDimensions.width,
9562
+ windowHeight = _useWindowDimensions.height;
9563
+ var maxSpotlightRadius = theme.__hd__.appCue.radii.spotlight;
9564
+ var backdropColor = theme.__hd__.appCue.colors.backdropColor;
9565
+ var resolvedRadius = Math.min(bounds.height / 2, bounds.width / 2, maxSpotlightRadius);
9566
+ return /*#__PURE__*/React__namespace.default.createElement(Svg__default.default, {
9567
+ width: windowWidth,
9568
+ height: windowHeight,
9569
+ style: {
9570
+ position: 'absolute',
9571
+ top: 0,
9572
+ left: 0
9573
+ }
9574
+ }, /*#__PURE__*/React__namespace.default.createElement(Svg.Defs, null, /*#__PURE__*/React__namespace.default.createElement(Svg.Mask, {
9575
+ id: "spotlight-mask"
9576
+ }, /*#__PURE__*/React__namespace.default.createElement(Svg.Rect, {
9577
+ width: windowWidth,
9578
+ height: windowHeight,
9579
+ fill: "white"
9580
+ }), /*#__PURE__*/React__namespace.default.createElement(Svg.Rect, {
9581
+ x: bounds.x,
9582
+ y: bounds.y,
9583
+ width: bounds.width,
9584
+ height: bounds.height,
9585
+ rx: resolvedRadius,
9586
+ ry: resolvedRadius,
9587
+ fill: "black",
9588
+ testID: testID && "".concat(testID, "-spotlight")
9589
+ }))), /*#__PURE__*/React__namespace.default.createElement(Svg.Rect, {
9590
+ width: windowWidth,
9591
+ height: windowHeight,
9592
+ fill: backdropColor,
9593
+ mask: "url(#spotlight-mask)"
9594
+ }));
9595
+ };
9596
+
9575
9597
  var calculatePosition = function calculatePosition(_ref) {
9576
9598
  var placement = _ref.placement,
9577
9599
  position = _ref.position,
@@ -9582,92 +9604,61 @@ var calculatePosition = function calculatePosition(_ref) {
9582
9604
  case 'top':
9583
9605
  {
9584
9606
  return {
9585
- // The X coordinate is calculated by adding the half of the width of the target element to the X coordinate of the target element.
9586
- left: position.pageX + (position.width - contentSize.width) / 2,
9587
- // The Y coordinate is calculated by subtracting the height of the content and the offset from the Y coordinate of the target element
9588
- top: position.pageY - contentSize.height - offset
9607
+ left: position.x + (position.width - contentSize.width) / 2,
9608
+ top: position.y - contentSize.height - offset
9589
9609
  };
9590
9610
  }
9591
9611
  case 'bottom':
9592
9612
  {
9593
9613
  return {
9594
- // The X coordinate is calculated by adding the half of the width of the target element to the X coordinate of the target element.
9595
- left: position.pageX + (position.width - contentSize.width) / 2,
9596
- // The Y coordinate is calculated by adding the height of the target element and the offset to the Y coordinate of the target element.
9597
- top: position.pageY + position.height + offset
9614
+ left: position.x + (position.width - contentSize.width) / 2,
9615
+ top: position.y + position.height + offset
9598
9616
  };
9599
9617
  }
9600
9618
  case 'right':
9601
9619
  {
9602
9620
  return {
9603
- // The X coordinate is calculated by adding the width of the target element and the offset to the X coordinate of the target element.
9604
- left: position.pageX + position.width + offset,
9605
- // The Y coordinate is calculated by adding half of the height of the target element to the Y coordinate of the target element.
9606
- top: position.pageY + (position.height - contentSize.height) / 2
9621
+ left: position.x + position.width + offset,
9622
+ top: position.y + (position.height - contentSize.height) / 2
9607
9623
  };
9608
9624
  }
9609
9625
  case 'left':
9610
9626
  {
9611
9627
  return {
9612
- // The X coordinate is calculated by subtracting the width of the content and the offset from the X coordinate of the target element.
9613
- left: position.pageX - contentSize.width - offset,
9614
- // The Y coordinate is calculated by adding half of the height of the target element to the Y coordinate of the target element.
9615
- top: position.pageY + (position.height - contentSize.height) / 2
9628
+ left: position.x - contentSize.width - offset,
9629
+ top: position.y + (position.height - contentSize.height) / 2
9616
9630
  };
9617
9631
  }
9618
9632
  case 'top-left':
9619
9633
  {
9620
9634
  return {
9621
- // The X coordinate is calculated by subtracting the width of the content from the X coordinate of the target element.
9622
- right: windowWidth - position.pageX - position.width,
9623
- // The Y coordinate is calculated by subtracting the height of the content and the offset from the Y coordinate of the target element
9624
- top: position.pageY - contentSize.height - offset
9635
+ right: windowWidth - position.x - position.width,
9636
+ top: position.y - contentSize.height - offset
9625
9637
  };
9626
9638
  }
9627
9639
  case 'top-right':
9628
9640
  {
9629
9641
  return {
9630
- // The X coordinate is calculated by adding the width of the target element to the X coordinate of the target element.
9631
- left: position.pageX,
9632
- // The Y coordinate is calculated by subtracting the height of the content and the offset from the Y coordinate of the target element.
9633
- top: position.pageY - contentSize.height - offset
9642
+ left: position.x,
9643
+ top: position.y - contentSize.height - offset
9634
9644
  };
9635
9645
  }
9636
9646
  case 'bottom-left':
9637
9647
  {
9638
9648
  return {
9639
- // The X coordinate is calculated by subtracting the width of the content from the X coordinate of the target element.
9640
- right: windowWidth - position.pageX - position.width,
9641
- // The Y coordinate is calculated by adding the height of the target element and the offset to the Y coordinate of the target element.
9642
- top: position.pageY + position.height + offset
9649
+ right: windowWidth - position.x - position.width,
9650
+ top: position.y + position.height + offset
9643
9651
  };
9644
9652
  }
9645
9653
  case 'bottom-right':
9646
9654
  {
9647
9655
  return {
9648
- // The X coordinate is calculated by adding the width of the target element to the X coordinate of the target element.
9649
- left: position.pageX,
9650
- // The Y coordinate is calculated by adding the height of the target element and the offset to the Y coordinate of the target element.
9651
- top: position.pageY + position.height + offset
9656
+ left: position.x,
9657
+ top: position.y + position.height + offset
9652
9658
  };
9653
9659
  }
9654
9660
  }
9655
9661
  };
9656
- /**
9657
- * Calculates the maximum width of the content based on its position, offset, placement, and window width.
9658
- *
9659
- * @param {Object} params - The parameters for the calculation.
9660
- * @param {Object} params.position - The position and dimensions of the target element.
9661
- * @param {number} params.position.pageX - The X coordinate of the target element.
9662
- * @param {number} params.position.pageY - The Y coordinate of the target element.
9663
- * @param {number} params.position.width - The width of the target element.
9664
- * @param {number} params.position.height - The height of the target element.
9665
- * @param {number} params.offset - The offset value to display an arrow.
9666
- * @param {Placement} params.placement - The placement of the content relative to the element.
9667
- * @param {number} params.windowWidth - The width of the window.
9668
- *
9669
- * @returns {number | undefined} The maximum width of the content.
9670
- */
9671
9662
  var calulateContentMaxWidth = function calulateContentMaxWidth(_ref2) {
9672
9663
  var position = _ref2.position,
9673
9664
  offset = _ref2.offset,
@@ -9684,27 +9675,27 @@ var calulateContentMaxWidth = function calulateContentMaxWidth(_ref2) {
9684
9675
  }
9685
9676
  case 'right':
9686
9677
  {
9687
- return windowWidth - position.pageX - position.width - offset;
9678
+ return windowWidth - position.x - position.width - offset;
9688
9679
  }
9689
9680
  case 'left':
9690
9681
  {
9691
- return position.pageX - offset;
9682
+ return position.x - offset;
9692
9683
  }
9693
9684
  case 'top-left':
9694
9685
  {
9695
- return position.pageX + position.width;
9686
+ return position.x + position.width;
9696
9687
  }
9697
9688
  case 'top-right':
9698
9689
  {
9699
- return windowWidth - position.pageX;
9690
+ return windowWidth - position.x;
9700
9691
  }
9701
9692
  case 'bottom-left':
9702
9693
  {
9703
- return position.pageX + position.width;
9694
+ return position.x + position.width;
9704
9695
  }
9705
9696
  case 'bottom-right':
9706
9697
  {
9707
- return windowWidth - position.pageX;
9698
+ return windowWidth - position.x;
9708
9699
  }
9709
9700
  }
9710
9701
  };
@@ -9715,23 +9706,27 @@ var AppCue = function AppCue(_ref) {
9715
9706
  _ref$placement = _ref.placement,
9716
9707
  placement = _ref$placement === void 0 ? 'top' : _ref$placement,
9717
9708
  style = _ref.style,
9718
- testID = _ref.testID;
9709
+ testID = _ref.testID,
9710
+ controlledVisible = _ref.visible,
9711
+ onDismiss = _ref.onDismiss;
9719
9712
  var targetContainerRef = React.useRef(null);
9713
+ var isControlled = controlledVisible !== undefined;
9720
9714
  var _React$useState = React__namespace.default.useState(false),
9721
9715
  _React$useState2 = _slicedToArray(_React$useState, 2),
9722
- visible = _React$useState2[0],
9723
- setVisible = _React$useState2[1];
9716
+ internalVisible = _React$useState2[0],
9717
+ setInternalVisible = _React$useState2[1];
9718
+ var open = isControlled ? controlledVisible : internalVisible;
9724
9719
  var theme = useTheme();
9725
9720
  var offset = theme.__hd__.appCue.space.offset;
9726
9721
  var _React$useState3 = React__namespace.default.useState({
9727
- pageX: 0,
9728
- pageY: 0,
9722
+ x: 0,
9723
+ y: 0,
9729
9724
  width: 0,
9730
9725
  height: 0
9731
9726
  }),
9732
9727
  _React$useState4 = _slicedToArray(_React$useState3, 2),
9733
- position = _React$useState4[0],
9734
- setPosition = _React$useState4[1];
9728
+ bounds = _React$useState4[0],
9729
+ setBounds = _React$useState4[1];
9735
9730
  var _React$useState5 = React__namespace.default.useState({
9736
9731
  width: 0,
9737
9732
  height: 0
@@ -9742,23 +9737,36 @@ var AppCue = function AppCue(_ref) {
9742
9737
  var doMeasure = React.useCallback(function (cb) {
9743
9738
  var _targetContainerRef$c;
9744
9739
  (_targetContainerRef$c = targetContainerRef.current) === null || _targetContainerRef$c === void 0 || _targetContainerRef$c.measure(function (_, __, width, height, pageX, pageY) {
9745
- setPosition({
9746
- pageX: pageX,
9747
- pageY: pageY,
9740
+ setBounds({
9741
+ x: pageX,
9742
+ y: pageY,
9748
9743
  width: width,
9749
9744
  height: height
9750
9745
  });
9751
9746
  cb === null || cb === void 0 || cb();
9752
9747
  });
9753
9748
  }, []);
9754
- var handleOpen = function handleOpen() {
9755
- doMeasure(function () {
9756
- return setVisible(true);
9757
- });
9758
- };
9749
+ var handleOpen = React.useCallback(function () {
9750
+ if (!isControlled) {
9751
+ doMeasure(function () {
9752
+ return setInternalVisible(true);
9753
+ });
9754
+ }
9755
+ }, [isControlled, doMeasure]);
9756
+ var handleDismiss = React.useCallback(function () {
9757
+ if (!isControlled) {
9758
+ setInternalVisible(false);
9759
+ }
9760
+ onDismiss === null || onDismiss === void 0 || onDismiss();
9761
+ }, [isControlled, onDismiss]);
9759
9762
  React.useLayoutEffect(function () {
9760
9763
  doMeasure();
9761
9764
  }, [doMeasure]);
9765
+ React.useEffect(function () {
9766
+ if (open) {
9767
+ doMeasure();
9768
+ }
9769
+ }, [open, doMeasure]);
9762
9770
  var enhancedTarget = /*#__PURE__*/React__namespace.default.cloneElement(target, {
9763
9771
  onPress: function onPress() {
9764
9772
  var _target$props, _target$props$onPress;
@@ -9775,14 +9783,14 @@ var AppCue = function AppCue(_ref) {
9775
9783
  var _useWindowDimensions = reactNative.useWindowDimensions(),
9776
9784
  windowWidth = _useWindowDimensions.width;
9777
9785
  var pos = calculatePosition({
9778
- position: position,
9786
+ position: bounds,
9779
9787
  contentSize: contentSize,
9780
9788
  placement: placement,
9781
9789
  offset: offset,
9782
9790
  windowWidth: windowWidth
9783
9791
  });
9784
9792
  var maxWidth = calulateContentMaxWidth({
9785
- position: position,
9793
+ position: bounds,
9786
9794
  offset: offset,
9787
9795
  placement: placement,
9788
9796
  windowWidth: windowWidth
@@ -9805,26 +9813,24 @@ var AppCue = function AppCue(_ref) {
9805
9813
  }
9806
9814
  }, enhancedTarget)), /*#__PURE__*/React__namespace.default.createElement(reactNative.Modal, {
9807
9815
  animationType: "fade",
9808
- visible: visible,
9809
- onDismiss: function onDismiss() {
9810
- return setVisible(false);
9811
- },
9816
+ visible: open,
9817
+ onDismiss: handleDismiss,
9812
9818
  transparent: true,
9813
9819
  presentationStyle: "overFullScreen",
9814
9820
  statusBarTranslucent: true
9815
9821
  }, /*#__PURE__*/React__namespace.default.createElement(reactNative.TouchableWithoutFeedback, {
9816
9822
  testID: testID && "".concat(testID, "-backdrop"),
9817
- onPress: function onPress() {
9818
- return setVisible(false);
9819
- }
9820
- }, /*#__PURE__*/React__namespace.default.createElement(StyledContainer$a, null, /*#__PURE__*/React__namespace.default.createElement(reactNative.View, {
9821
- style: reactNative.StyleSheet.flatten([{
9822
- position: 'absolute',
9823
+ onPress: handleDismiss
9824
+ }, /*#__PURE__*/React__namespace.default.createElement(StyledModalContainer, null, /*#__PURE__*/React__namespace.default.createElement(SpotlightBackdrop, {
9825
+ bounds: bounds,
9826
+ testID: testID
9827
+ }), /*#__PURE__*/React__namespace.default.createElement(StyledContentPositioner, {
9828
+ style: [{
9823
9829
  top: pos.top,
9824
9830
  left: pos.left,
9825
9831
  bottom: pos.bottom,
9826
9832
  right: pos.right
9827
- }, style]),
9833
+ }, style],
9828
9834
  onLayout: measureContent,
9829
9835
  testID: testID
9830
9836
  }, /*#__PURE__*/React__namespace.default.createElement(StyledContent$1, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hero-design/rn",
3
- "version": "8.132.1",
3
+ "version": "8.133.0",
4
4
  "license": "MIT",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.js",
@@ -0,0 +1,60 @@
1
+ import React from 'react';
2
+ import { useWindowDimensions } from 'react-native';
3
+ import Svg, { Defs, Mask, Rect } from 'react-native-svg';
4
+ import { useTheme } from '../../theme';
5
+
6
+ interface SpotlightBackdropProps {
7
+ bounds: {
8
+ x: number;
9
+ y: number;
10
+ width: number;
11
+ height: number;
12
+ };
13
+ testID?: string;
14
+ }
15
+
16
+ const SpotlightBackdrop = ({ bounds, testID }: SpotlightBackdropProps) => {
17
+ const theme = useTheme();
18
+ const { width: windowWidth, height: windowHeight } = useWindowDimensions();
19
+
20
+ const { spotlight: maxSpotlightRadius } = theme.__hd__.appCue.radii;
21
+ const { backdropColor } = theme.__hd__.appCue.colors;
22
+
23
+ const resolvedRadius = Math.min(
24
+ bounds.height / 2,
25
+ bounds.width / 2,
26
+ maxSpotlightRadius
27
+ );
28
+
29
+ return (
30
+ <Svg
31
+ width={windowWidth}
32
+ height={windowHeight}
33
+ style={{ position: 'absolute', top: 0, left: 0 }}
34
+ >
35
+ <Defs>
36
+ <Mask id="spotlight-mask">
37
+ <Rect width={windowWidth} height={windowHeight} fill="white" />
38
+ <Rect
39
+ x={bounds.x}
40
+ y={bounds.y}
41
+ width={bounds.width}
42
+ height={bounds.height}
43
+ rx={resolvedRadius}
44
+ ry={resolvedRadius}
45
+ fill="black"
46
+ testID={testID && `${testID}-spotlight`}
47
+ />
48
+ </Mask>
49
+ </Defs>
50
+ <Rect
51
+ width={windowWidth}
52
+ height={windowHeight}
53
+ fill={backdropColor}
54
+ mask="url(#spotlight-mask)"
55
+ />
56
+ </Svg>
57
+ );
58
+ };
59
+
60
+ export default SpotlightBackdrop;
@@ -18,11 +18,14 @@ export const StyledContent = styled(View)(({ theme }) => ({
18
18
  alignSelf: 'center',
19
19
  }));
20
20
 
21
- export const StyledContainer = styled(View)(({ theme }) => ({
21
+ export const StyledModalContainer = styled(View)({
22
22
  width: '100%',
23
23
  height: '100%',
24
- backgroundColor: theme.__hd__.appCue.colors.backdropColor,
25
- }));
24
+ });
25
+
26
+ export const StyledContentPositioner = styled(View)({
27
+ position: 'absolute',
28
+ });
26
29
 
27
30
  export const StyledIconContainer = styled(Animated.View)<{
28
31
  themePlacement: Placement;
@@ -1,20 +1,22 @@
1
- import React, { useCallback, useLayoutEffect, useRef } from 'react';
1
+ import React, { useCallback, useEffect, useLayoutEffect, useRef } from 'react';
2
2
  import type { LayoutChangeEvent, StyleProp, ViewStyle } from 'react-native';
3
3
  import {
4
4
  Modal,
5
5
  View,
6
6
  TouchableWithoutFeedback,
7
- StyleSheet,
8
7
  useWindowDimensions,
9
8
  } from 'react-native';
10
9
  import type { Placement } from './StyledAppCue';
11
10
  import {
12
- StyledContainer,
13
11
  StyledContent,
12
+ StyledContentPositioner,
14
13
  StyledIconContainer,
14
+ StyledModalContainer,
15
15
  } from './StyledAppCue';
16
+ import SpotlightBackdrop from './SpotlightBackdrop';
16
17
  import Typography from '../Typography';
17
18
  import Icon from '../Icon';
19
+
18
20
  import { useTheme } from '../../theme';
19
21
  import { calculatePosition, calulateContentMaxWidth } from './utils';
20
22
 
@@ -43,6 +45,16 @@ export interface AppCueProps {
43
45
  * Testing ID of the component.
44
46
  */
45
47
  testID?: string;
48
+ /**
49
+ * Controls visibility externally. When provided, the component becomes controlled
50
+ * and will not toggle open/closed on target press.
51
+ */
52
+ visible?: boolean;
53
+ /**
54
+ * Called when the backdrop is pressed. Use this in controlled mode to update
55
+ * the `visible` prop.
56
+ */
57
+ onDismiss?: () => void;
46
58
  }
47
59
 
48
60
  const AppCue = ({
@@ -51,18 +63,22 @@ const AppCue = ({
51
63
  placement = 'top',
52
64
  style,
53
65
  testID,
66
+ visible: controlledVisible,
67
+ onDismiss,
54
68
  }: AppCueProps) => {
55
69
  const targetContainerRef = useRef<View>(null);
56
70
 
57
- const [visible, setVisible] = React.useState(false);
71
+ const isControlled = controlledVisible !== undefined;
72
+ const [internalVisible, setInternalVisible] = React.useState(false);
73
+ const open = isControlled ? controlledVisible : internalVisible;
58
74
 
59
75
  const theme = useTheme();
60
76
 
61
77
  const { offset } = theme.__hd__.appCue.space;
62
78
 
63
- const [position, setPosition] = React.useState({
64
- pageX: 0,
65
- pageY: 0,
79
+ const [bounds, setBounds] = React.useState({
80
+ x: 0,
81
+ y: 0,
66
82
  width: 0,
67
83
  height: 0,
68
84
  });
@@ -75,20 +91,35 @@ const AppCue = ({
75
91
  const doMeasure = useCallback((cb?: () => void) => {
76
92
  targetContainerRef.current?.measure(
77
93
  (_, __, width, height, pageX, pageY) => {
78
- setPosition({ pageX, pageY, width, height });
94
+ setBounds({ x: pageX, y: pageY, width, height });
79
95
  cb?.();
80
96
  }
81
97
  );
82
98
  }, []);
83
99
 
84
- const handleOpen = () => {
85
- doMeasure(() => setVisible(true));
86
- };
100
+ const handleOpen = useCallback(() => {
101
+ if (!isControlled) {
102
+ doMeasure(() => setInternalVisible(true));
103
+ }
104
+ }, [isControlled, doMeasure]);
105
+
106
+ const handleDismiss = useCallback(() => {
107
+ if (!isControlled) {
108
+ setInternalVisible(false);
109
+ }
110
+ onDismiss?.();
111
+ }, [isControlled, onDismiss]);
87
112
 
88
113
  useLayoutEffect(() => {
89
114
  doMeasure();
90
115
  }, [doMeasure]);
91
116
 
117
+ useEffect(() => {
118
+ if (open) {
119
+ doMeasure();
120
+ }
121
+ }, [open, doMeasure]);
122
+
92
123
  const enhancedTarget = React.cloneElement(
93
124
  target,
94
125
  {
@@ -110,7 +141,7 @@ const AppCue = ({
110
141
  const { width: windowWidth } = useWindowDimensions();
111
142
 
112
143
  const pos = calculatePosition({
113
- position,
144
+ position: bounds,
114
145
  contentSize,
115
146
  placement,
116
147
  offset,
@@ -118,7 +149,7 @@ const AppCue = ({
118
149
  });
119
150
 
120
151
  const maxWidth = calulateContentMaxWidth({
121
- position,
152
+ position: bounds,
122
153
  offset,
123
154
  placement,
124
155
  windowWidth,
@@ -144,28 +175,28 @@ const AppCue = ({
144
175
  </TouchableWithoutFeedback>
145
176
  <Modal
146
177
  animationType="fade"
147
- visible={visible}
148
- onDismiss={() => setVisible(false)}
178
+ visible={open}
179
+ onDismiss={handleDismiss}
149
180
  transparent
150
181
  presentationStyle="overFullScreen"
151
182
  statusBarTranslucent
152
183
  >
153
184
  <TouchableWithoutFeedback
154
185
  testID={testID && `${testID}-backdrop`}
155
- onPress={() => setVisible(false)}
186
+ onPress={handleDismiss}
156
187
  >
157
- <StyledContainer>
158
- <View
159
- style={StyleSheet.flatten([
188
+ <StyledModalContainer>
189
+ <SpotlightBackdrop bounds={bounds} testID={testID} />
190
+ <StyledContentPositioner
191
+ style={[
160
192
  {
161
- position: 'absolute',
162
193
  top: pos.top,
163
194
  left: pos.left,
164
195
  bottom: pos.bottom,
165
196
  right: pos.right,
166
197
  },
167
198
  style,
168
- ])}
199
+ ]}
169
200
  onLayout={measureContent}
170
201
  testID={testID}
171
202
  >
@@ -181,8 +212,8 @@ const AppCue = ({
181
212
  >
182
213
  <Icon icon="caret-down" size="small" />
183
214
  </StyledIconContainer>
184
- </View>
185
- </StyledContainer>
215
+ </StyledContentPositioner>
216
+ </StyledModalContainer>
186
217
  </TouchableWithoutFeedback>
187
218
  </Modal>
188
219
  </>