@hero-design/rn 8.82.2-alpha.2 → 8.83.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.
Files changed (56) hide show
  1. package/.turbo/turbo-build.log +7 -0
  2. package/CHANGELOG.md +20 -0
  3. package/es/index.js +491 -21
  4. package/lib/index.js +490 -18
  5. package/package.json +6 -4
  6. package/rollup.config.mjs +1 -0
  7. package/src/components/AppCue/StyledAppCue.tsx +46 -0
  8. package/src/components/AppCue/__tests__/StyledAppCue.tsx +18 -0
  9. package/src/components/AppCue/__tests__/__snapshots__/StyledAppCue.tsx.snap +200 -0
  10. package/src/components/AppCue/__tests__/__snapshots__/index.spec.tsx.snap +391 -0
  11. package/src/components/AppCue/__tests__/index.spec.tsx +61 -0
  12. package/src/components/AppCue/__tests__/utils.spec.ts +90 -0
  13. package/src/components/AppCue/index.tsx +188 -0
  14. package/src/components/AppCue/utils.ts +122 -0
  15. package/src/components/Chip/StyledChip.tsx +9 -9
  16. package/src/components/Chip/__tests__/__snapshots__/index.spec.tsx.snap +434 -0
  17. package/src/components/Chip/__tests__/index.spec.tsx +4 -0
  18. package/src/components/Chip/index.tsx +32 -5
  19. package/src/components/Slider/RangeSlider.tsx +187 -0
  20. package/src/components/Slider/SingleSlider.tsx +89 -0
  21. package/src/components/Slider/StyledRangeSlider.tsx +16 -0
  22. package/src/components/Slider/__tests__/RangeSlider.spec.tsx +119 -0
  23. package/src/components/Slider/__tests__/{index.spec.tsx → SingleSlider.spec.tsx} +1 -1
  24. package/src/components/Slider/__tests__/__snapshots__/RangeSlider.spec.tsx.snap +252 -0
  25. package/src/components/Slider/index.tsx +8 -83
  26. package/src/components/Tabs/StyledScrollableTabs.tsx +2 -1
  27. package/src/components/Tabs/StyledTabs.tsx +1 -0
  28. package/src/components/Tabs/__tests__/__snapshots__/ScrollableTabs.spec.tsx.snap +3 -0
  29. package/src/components/Tabs/__tests__/__snapshots__/ScrollableTabsHeader.spec.tsx.snap +2 -0
  30. package/src/components/Tabs/__tests__/__snapshots__/index.spec.tsx.snap +3 -0
  31. package/src/index.ts +2 -0
  32. package/src/theme/__tests__/__snapshots__/index.spec.ts.snap +39 -0
  33. package/src/theme/components/appCue.ts +22 -0
  34. package/src/theme/components/slider.ts +19 -1
  35. package/src/theme/components/tabs.ts +1 -0
  36. package/src/theme/getTheme.ts +3 -0
  37. package/src/types.ts +2 -0
  38. package/stats/8.83.0/rn-stats.html +4844 -0
  39. package/testUtils/setup.tsx +17 -0
  40. package/types/components/AppCue/StyledAppCue.d.ts +20 -0
  41. package/types/components/AppCue/__tests__/StyledAppCue.d.ts +1 -0
  42. package/types/components/AppCue/index.d.ts +21 -0
  43. package/types/components/AppCue/utils.d.ts +63 -0
  44. package/types/components/Chip/StyledChip.d.ts +1 -1
  45. package/types/components/Chip/index.d.ts +2 -2
  46. package/types/components/Slider/RangeSlider.d.ts +60 -0
  47. package/types/components/Slider/SingleSlider.d.ts +53 -0
  48. package/types/components/Slider/StyledRangeSlider.d.ts +10 -0
  49. package/types/components/Slider/index.d.ts +6 -51
  50. package/types/index.d.ts +2 -1
  51. package/types/theme/components/appCue.d.ts +16 -0
  52. package/types/theme/components/slider.d.ts +24 -0
  53. package/types/theme/components/tabs.d.ts +1 -0
  54. package/types/theme/getTheme.d.ts +2 -0
  55. package/types/types.d.ts +2 -1
  56. /package/src/components/Slider/__tests__/__snapshots__/{index.spec.tsx.snap → SingleSlider.spec.tsx.snap} +0 -0
@@ -0,0 +1,90 @@
1
+ import { calculatePosition, calulateContentMaxWidth } from '../utils';
2
+
3
+ describe('utils', () => {
4
+ describe('calculatePosition', () => {
5
+ const testCases = [
6
+ {
7
+ placement: 'top' as const,
8
+ position: { pageX: 100, pageY: 200, width: 50, height: 50 },
9
+ contentSize: { width: 30, height: 20 },
10
+ offset: 10,
11
+ expected: { x: 110, y: 170 },
12
+ },
13
+ {
14
+ placement: 'bottom' as const,
15
+ position: { pageX: 100, pageY: 200, width: 50, height: 50 },
16
+ contentSize: { width: 30, height: 20 },
17
+ offset: 10,
18
+ expected: { x: 110, y: 260 },
19
+ },
20
+ {
21
+ placement: 'right' as const,
22
+ position: { pageX: 100, pageY: 200, width: 50, height: 50 },
23
+ contentSize: { width: 30, height: 20 },
24
+ offset: 10,
25
+ expected: { x: 160, y: 215 },
26
+ },
27
+ {
28
+ placement: 'left' as const,
29
+ position: { pageX: 100, pageY: 200, width: 50, height: 50 },
30
+ contentSize: { width: 30, height: 20 },
31
+ offset: 10,
32
+ expected: { x: 60, y: 215 },
33
+ },
34
+ ];
35
+ it.each(testCases)(
36
+ 'should calculate the correct position for placement $placement',
37
+ ({ placement, position, contentSize, offset, expected }) => {
38
+ const result = calculatePosition({
39
+ placement,
40
+ position,
41
+ contentSize,
42
+ offset,
43
+ });
44
+ expect(result).toEqual(expected);
45
+ }
46
+ );
47
+ });
48
+
49
+ describe('calulateContentMaxWidth', () => {
50
+ const windowWidth = 1024;
51
+ const testCases = [
52
+ {
53
+ placement: 'top' as const,
54
+ position: { pageX: 100, pageY: 200, width: 50, height: 50 },
55
+ offset: 10,
56
+ expected: undefined,
57
+ },
58
+ {
59
+ placement: 'bottom' as const,
60
+ position: { pageX: 100, pageY: 200, width: 50, height: 50 },
61
+ offset: 10,
62
+ expected: undefined,
63
+ },
64
+ {
65
+ placement: 'right' as const,
66
+ position: { pageX: 100, pageY: 200, width: 50, height: 50 },
67
+ offset: 10,
68
+ expected: windowWidth - 100 - 50 - 10,
69
+ },
70
+ {
71
+ placement: 'left' as const,
72
+ position: { pageX: 100, pageY: 200, width: 50, height: 50 },
73
+ offset: 10,
74
+ expected: 100 - 10,
75
+ },
76
+ ];
77
+ it.each(testCases)(
78
+ 'should calculate the correct max width for placement $placement',
79
+ ({ placement, position, offset, expected }) => {
80
+ const result = calulateContentMaxWidth({
81
+ placement,
82
+ position,
83
+ offset,
84
+ windowWidth,
85
+ });
86
+ expect(result).toEqual(expected);
87
+ }
88
+ );
89
+ });
90
+ });
@@ -0,0 +1,188 @@
1
+ import React, { useCallback, useLayoutEffect, useRef } from 'react';
2
+ import {
3
+ Modal,
4
+ View,
5
+ TouchableWithoutFeedback,
6
+ LayoutChangeEvent,
7
+ StyleProp,
8
+ ViewStyle,
9
+ StyleSheet,
10
+ useWindowDimensions,
11
+ } from 'react-native';
12
+ import {
13
+ Placement,
14
+ StyledContainer,
15
+ StyledContent,
16
+ StyledIconContainer,
17
+ } from './StyledAppCue';
18
+ import Typography from '../Typography';
19
+ import Icon from '../Icon';
20
+ import { useTheme } from '../../theme';
21
+ import { calculatePosition, calulateContentMaxWidth } from './utils';
22
+
23
+ interface AppCueProps {
24
+ /*
25
+ * The content of the App Cue.
26
+ */
27
+ content: string | React.ReactElement;
28
+ /*
29
+ * The target where the App Cue is relatively placed to.
30
+ */
31
+ target: React.ReactElement;
32
+
33
+ /**
34
+ * The Position of the App Cue.
35
+ */
36
+ placement?: Placement;
37
+ /**
38
+ * Additional style.
39
+ */
40
+ style?: StyleProp<ViewStyle>;
41
+ /**
42
+ * Testing ID of the component.
43
+ */
44
+ testID?: string;
45
+ }
46
+
47
+ const AppCue = ({
48
+ target,
49
+ content,
50
+ placement = 'top',
51
+ style,
52
+ testID,
53
+ }: AppCueProps) => {
54
+ const targetContainerRef = useRef<View>(null);
55
+
56
+ const [visible, setVisible] = React.useState(false);
57
+
58
+ const theme = useTheme();
59
+
60
+ const { offset } = theme.__hd__.appCue.space;
61
+
62
+ const [position, setPosition] = React.useState({
63
+ pageX: 0,
64
+ pageY: 0,
65
+ width: 0,
66
+ height: 0,
67
+ });
68
+
69
+ const [contentSize, setContentSize] = React.useState({
70
+ width: 0,
71
+ height: 0,
72
+ });
73
+
74
+ const doMeasure = useCallback((cb?: () => void) => {
75
+ targetContainerRef.current?.measure(
76
+ (_, __, width, height, pageX, pageY) => {
77
+ setPosition({ pageX, pageY, width, height });
78
+ cb?.();
79
+ }
80
+ );
81
+ }, []);
82
+
83
+ const handleOpen = () => {
84
+ doMeasure(() => setVisible(true));
85
+ };
86
+
87
+ useLayoutEffect(() => {
88
+ doMeasure();
89
+ }, [doMeasure]);
90
+
91
+ const enhancedTarget = React.cloneElement(
92
+ target,
93
+ {
94
+ onPress: () => {
95
+ handleOpen();
96
+ target.props?.onPress?.();
97
+ },
98
+ },
99
+ target.props.children
100
+ );
101
+
102
+ const measureContent = (event: LayoutChangeEvent) => {
103
+ setContentSize({
104
+ width: event.nativeEvent.layout.width,
105
+ height: event.nativeEvent.layout.height,
106
+ });
107
+ };
108
+
109
+ const pos = calculatePosition({
110
+ position,
111
+ contentSize,
112
+ placement,
113
+ offset,
114
+ });
115
+
116
+ const { width: windowWidth } = useWindowDimensions();
117
+
118
+ const maxWidth = calulateContentMaxWidth({
119
+ position,
120
+ offset,
121
+ placement,
122
+ windowWidth,
123
+ });
124
+
125
+ const renderContent = () => {
126
+ if (typeof content === 'string') {
127
+ return <Typography.Body intent="inverted">{content}</Typography.Body>;
128
+ }
129
+ return content;
130
+ };
131
+
132
+ return (
133
+ <>
134
+ <TouchableWithoutFeedback onPress={handleOpen}>
135
+ <View
136
+ collapsable={false}
137
+ ref={targetContainerRef}
138
+ style={{ alignSelf: 'center' }}
139
+ >
140
+ {enhancedTarget}
141
+ </View>
142
+ </TouchableWithoutFeedback>
143
+ <Modal
144
+ animationType="fade"
145
+ visible={visible}
146
+ onDismiss={() => setVisible(false)}
147
+ transparent
148
+ presentationStyle="overFullScreen"
149
+ statusBarTranslucent
150
+ >
151
+ <TouchableWithoutFeedback
152
+ testID={testID && `${testID}-backdrop`}
153
+ onPress={() => setVisible(false)}
154
+ >
155
+ <StyledContainer>
156
+ <View
157
+ style={StyleSheet.flatten([
158
+ {
159
+ position: 'absolute',
160
+ top: pos.y,
161
+ left: pos.x,
162
+ },
163
+ style,
164
+ ])}
165
+ onLayout={measureContent}
166
+ testID={testID}
167
+ >
168
+ <StyledContent
169
+ style={{ maxWidth }}
170
+ testID={testID && `${testID}-content`}
171
+ >
172
+ {renderContent()}
173
+ </StyledContent>
174
+ <StyledIconContainer
175
+ themePlacement={placement}
176
+ testID={testID && `${testID}-arrow`}
177
+ >
178
+ <Icon icon="caret-down" size="small" />
179
+ </StyledIconContainer>
180
+ </View>
181
+ </StyledContainer>
182
+ </TouchableWithoutFeedback>
183
+ </Modal>
184
+ </>
185
+ );
186
+ };
187
+
188
+ export default AppCue;
@@ -0,0 +1,122 @@
1
+ import { Placement } from './StyledAppCue';
2
+
3
+ /**
4
+ * Calculates the position of an element based on its placement relative to a reference position.
5
+ *
6
+ * @param {Object} params - The parameters for calculating the position.
7
+ * @param {Object} params.position - The position and size of target element.
8
+ * @param {number} params.position.pageX - The X coordinate of the target position.
9
+ * @param {number} params.position.pageY - The Y coordinate of the target position.
10
+ * @param {number} params.position.width - The width of the reference element.
11
+ * @param {number} params.position.height - The height of the reference element.
12
+ * @param {Object} params.contentSize - The size of the App Cue content.
13
+ * @param {number} params.contentSize.width - The width of the content.
14
+ * @param {number} params.contentSize.height - The height of the content.
15
+ * @param {Placement} params.placement - The placement of the content relative to the reference position ('top', 'bottom', 'right', 'left').
16
+ * @param {number} params.offset - The offset distance to display an arrow.
17
+ *
18
+ * @returns {Object} The calculated position of the App Cue.
19
+ * @returns {number} return.x - The X coordinate of the App Cue.
20
+ * @returns {number} return.y - The Y coordinate of the App Cue.
21
+ */
22
+ export const calculatePosition = ({
23
+ placement,
24
+ position,
25
+ contentSize,
26
+ offset,
27
+ }: {
28
+ position: {
29
+ pageX: number;
30
+ pageY: number;
31
+ width: number;
32
+ height: number;
33
+ };
34
+ contentSize: {
35
+ width: number;
36
+ height: number;
37
+ };
38
+ placement: Placement;
39
+ offset: number;
40
+ }) => {
41
+ switch (placement) {
42
+ case 'top': {
43
+ return {
44
+ // The X coordinate is calculated by adding the half of the width of the target element to the X coordinate of the target element.
45
+ x: position.pageX + (position.width - contentSize.width) / 2,
46
+ // The Y coordinate is calculated by subtracting the height of the content and the offset from the Y coordinate of the target element
47
+ y: position.pageY - contentSize.height - offset,
48
+ };
49
+ }
50
+ case 'bottom': {
51
+ return {
52
+ // The X coordinate is calculated by adding the half of the width of the target element to the X coordinate of the target element.
53
+ x: position.pageX + (position.width - contentSize.width) / 2,
54
+ // The Y coordinate is calculated by adding the height of the target element and the offset to the Y coordinate of the target element.
55
+ y: position.pageY + position.height + offset,
56
+ };
57
+ }
58
+ case 'right': {
59
+ return {
60
+ // The X coordinate is calculated by adding the width of the target element and the offset to the X coordinate of the target element.
61
+ x: position.pageX + position.width + offset,
62
+ // The Y coordinate is calculated by adding half of the height of the target element to the Y coordinate of the target element.
63
+ y: position.pageY + (position.height - contentSize.height) / 2,
64
+ };
65
+ }
66
+ case 'left': {
67
+ return {
68
+ // The X coordinate is calculated by subtracting the width of the content and the offset from the X coordinate of the target element.
69
+ x: position.pageX - contentSize.width - offset,
70
+ // The Y coordinate is calculated by adding half of the height of the target element to the Y coordinate of the target element.
71
+ y: position.pageY + (position.height - contentSize.height) / 2,
72
+ };
73
+ }
74
+ }
75
+ };
76
+
77
+ /**
78
+ * Calculates the maximum width of the content based on its position, offset, placement, and window width.
79
+ *
80
+ * @param {Object} params - The parameters for the calculation.
81
+ * @param {Object} params.position - The position and dimensions of the target element.
82
+ * @param {number} params.position.pageX - The X coordinate of the target element.
83
+ * @param {number} params.position.pageY - The Y coordinate of the target element.
84
+ * @param {number} params.position.width - The width of the target element.
85
+ * @param {number} params.position.height - The height of the target element.
86
+ * @param {number} params.offset - The offset value to display an arrow.
87
+ * @param {Placement} params.placement - The placement of the content relative to the element.
88
+ * @param {number} params.windowWidth - The width of the window.
89
+ *
90
+ * @returns {number | undefined} The maximum width of the content.
91
+ */
92
+ export const calulateContentMaxWidth = ({
93
+ position,
94
+ offset,
95
+ placement,
96
+ windowWidth,
97
+ }: {
98
+ position: {
99
+ pageX: number;
100
+ pageY: number;
101
+ width: number;
102
+ height: number;
103
+ };
104
+ offset: number;
105
+ placement: Placement;
106
+ windowWidth: number;
107
+ }) => {
108
+ switch (placement) {
109
+ case 'top': {
110
+ return undefined;
111
+ }
112
+ case 'bottom': {
113
+ return undefined;
114
+ }
115
+ case 'right': {
116
+ return windowWidth - position.pageX - position.width - offset;
117
+ }
118
+ case 'left': {
119
+ return position.pageX - offset;
120
+ }
121
+ }
122
+ };
@@ -3,7 +3,7 @@ import { TouchableOpacity } from 'react-native';
3
3
  import Icon from '../Icon';
4
4
 
5
5
  type StyledChipWrapperProps = {
6
- themeVariant?: 'outlined' | 'filled' | 'compact' | 'compact-outlined';
6
+ themeVariant?: 'selection' | 'filter' | 'compact' | 'compact-outlined';
7
7
  themeSelected?: boolean;
8
8
  };
9
9
 
@@ -12,11 +12,11 @@ const StyledChipWrapper = styled(TouchableOpacity)<StyledChipWrapperProps>(
12
12
  const getShadowStyles = () => {
13
13
  switch (themeVariant) {
14
14
  case 'compact':
15
- case 'filled':
15
+ case 'filter':
16
16
  return {
17
17
  ...theme.__hd__.chip.shadows.filledWrapper,
18
18
  };
19
- case 'outlined':
19
+ case 'selection':
20
20
  case 'compact-outlined':
21
21
  return undefined;
22
22
  }
@@ -24,7 +24,7 @@ const StyledChipWrapper = styled(TouchableOpacity)<StyledChipWrapperProps>(
24
24
 
25
25
  const getBorderStyles = () => {
26
26
  switch (themeVariant) {
27
- case 'outlined':
27
+ case 'selection':
28
28
  case 'compact-outlined': {
29
29
  return {
30
30
  borderColor: themeSelected
@@ -34,7 +34,7 @@ const StyledChipWrapper = styled(TouchableOpacity)<StyledChipWrapperProps>(
34
34
  };
35
35
  }
36
36
  case 'compact':
37
- case 'filled': {
37
+ case 'filter': {
38
38
  return {
39
39
  borderColor: theme.__hd__.chip.colors.wrapperSelectedBorder,
40
40
  };
@@ -45,7 +45,7 @@ const StyledChipWrapper = styled(TouchableOpacity)<StyledChipWrapperProps>(
45
45
  const getBackgroundStyles = () => {
46
46
  if (themeSelected) {
47
47
  switch (themeVariant) {
48
- case 'outlined':
48
+ case 'selection':
49
49
  case 'compact-outlined': {
50
50
  return {
51
51
  backgroundColor: themeSelected
@@ -53,7 +53,7 @@ const StyledChipWrapper = styled(TouchableOpacity)<StyledChipWrapperProps>(
53
53
  : theme.__hd__.chip.colors.outlinedDefaultBackground,
54
54
  };
55
55
  }
56
- case 'filled':
56
+ case 'filter':
57
57
  case 'compact': {
58
58
  return {
59
59
  backgroundColor: theme.__hd__.chip.colors.secondaryBackground,
@@ -62,14 +62,14 @@ const StyledChipWrapper = styled(TouchableOpacity)<StyledChipWrapperProps>(
62
62
  }
63
63
  } else {
64
64
  switch (themeVariant) {
65
- case 'outlined':
65
+ case 'selection':
66
66
  case 'compact-outlined': {
67
67
  return {
68
68
  backgroundColor:
69
69
  theme.__hd__.chip.colors.outlinedDefaultBackground,
70
70
  };
71
71
  }
72
- case 'filled':
72
+ case 'filter':
73
73
  case 'compact': {
74
74
  return {
75
75
  backgroundColor: theme.__hd__.chip.colors.filledBackground,