@hero-design/rn 8.132.0 → 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/CHANGELOG.md +12 -0
- package/assets/fonts/hero-icons-mobile.ttf +0 -0
- package/es/index.js +118 -112
- package/lib/assets/fonts/hero-icons-mobile.ttf +0 -0
- package/lib/index.js +118 -112
- package/package.json +1 -1
- package/src/components/AppCue/SpotlightBackdrop.tsx +60 -0
- package/src/components/AppCue/StyledAppCue.tsx +6 -3
- package/src/components/AppCue/index.tsx +54 -23
- package/src/components/AppCue/utils.ts +26 -76
- package/src/theme/components/appCue.ts +1 -0
- package/types/components/AppCue/SpotlightBackdrop.d.ts +12 -0
- package/types/components/AppCue/StyledAppCue.d.ts +7 -1
- package/types/components/AppCue/index.d.ts +11 -1
- package/types/components/AppCue/utils.d.ts +4 -38
- package/types/theme/components/appCue.d.ts +1 -0
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
|
|
9498
|
-
|
|
9499
|
-
|
|
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
|
|
9506
|
-
|
|
9507
|
-
|
|
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
|
-
|
|
9558
|
-
|
|
9559
|
-
|
|
9560
|
-
|
|
9561
|
-
|
|
9562
|
-
|
|
9563
|
-
|
|
9564
|
-
|
|
9565
|
-
|
|
9566
|
-
|
|
9567
|
-
|
|
9568
|
-
|
|
9569
|
-
|
|
9570
|
-
|
|
9571
|
-
|
|
9572
|
-
|
|
9573
|
-
|
|
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
|
-
|
|
9586
|
-
|
|
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
|
-
|
|
9595
|
-
|
|
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
|
-
|
|
9604
|
-
|
|
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
|
-
|
|
9613
|
-
|
|
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
|
-
|
|
9622
|
-
|
|
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
|
-
|
|
9631
|
-
|
|
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
|
-
|
|
9640
|
-
|
|
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
|
-
|
|
9649
|
-
|
|
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.
|
|
9678
|
+
return windowWidth - position.x - position.width - offset;
|
|
9688
9679
|
}
|
|
9689
9680
|
case 'left':
|
|
9690
9681
|
{
|
|
9691
|
-
return position.
|
|
9682
|
+
return position.x - offset;
|
|
9692
9683
|
}
|
|
9693
9684
|
case 'top-left':
|
|
9694
9685
|
{
|
|
9695
|
-
return position.
|
|
9686
|
+
return position.x + position.width;
|
|
9696
9687
|
}
|
|
9697
9688
|
case 'top-right':
|
|
9698
9689
|
{
|
|
9699
|
-
return windowWidth - position.
|
|
9690
|
+
return windowWidth - position.x;
|
|
9700
9691
|
}
|
|
9701
9692
|
case 'bottom-left':
|
|
9702
9693
|
{
|
|
9703
|
-
return position.
|
|
9694
|
+
return position.x + position.width;
|
|
9704
9695
|
}
|
|
9705
9696
|
case 'bottom-right':
|
|
9706
9697
|
{
|
|
9707
|
-
return windowWidth - position.
|
|
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
|
-
|
|
9723
|
-
|
|
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
|
-
|
|
9728
|
-
|
|
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
|
-
|
|
9734
|
-
|
|
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
|
-
|
|
9746
|
-
|
|
9747
|
-
|
|
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
|
|
9755
|
-
|
|
9756
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
9809
|
-
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:
|
|
9818
|
-
|
|
9819
|
-
|
|
9820
|
-
|
|
9821
|
-
|
|
9822
|
-
|
|
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
|
@@ -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
|
|
21
|
+
export const StyledModalContainer = styled(View)({
|
|
22
22
|
width: '100%',
|
|
23
23
|
height: '100%',
|
|
24
|
-
|
|
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
|
|
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 [
|
|
64
|
-
|
|
65
|
-
|
|
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
|
-
|
|
94
|
+
setBounds({ x: pageX, y: pageY, width, height });
|
|
79
95
|
cb?.();
|
|
80
96
|
}
|
|
81
97
|
);
|
|
82
98
|
}, []);
|
|
83
99
|
|
|
84
|
-
const handleOpen = () => {
|
|
85
|
-
|
|
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={
|
|
148
|
-
onDismiss={
|
|
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={
|
|
186
|
+
onPress={handleDismiss}
|
|
156
187
|
>
|
|
157
|
-
<
|
|
158
|
-
<
|
|
159
|
-
|
|
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
|
-
</
|
|
185
|
-
</
|
|
215
|
+
</StyledContentPositioner>
|
|
216
|
+
</StyledModalContainer>
|
|
186
217
|
</TouchableWithoutFeedback>
|
|
187
218
|
</Modal>
|
|
188
219
|
</>
|