@utilitywarehouse/hearth-react-native 0.27.3 → 0.28.0-testid-fix-1

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 (150) hide show
  1. package/.turbo/turbo-build.log +5 -4
  2. package/.turbo/turbo-lint.log +70 -69
  3. package/CHANGELOG.md +110 -0
  4. package/build/components/Combobox/Combobox.context.d.ts +13 -0
  5. package/build/components/Combobox/Combobox.context.js +9 -0
  6. package/build/components/Combobox/Combobox.d.ts +6 -0
  7. package/build/components/Combobox/Combobox.js +246 -0
  8. package/build/components/Combobox/Combobox.props.d.ts +180 -0
  9. package/build/components/Combobox/Combobox.props.js +1 -0
  10. package/build/components/Combobox/ComboboxOption.d.ts +6 -0
  11. package/build/components/Combobox/ComboboxOption.js +56 -0
  12. package/build/components/Combobox/index.d.ts +4 -0
  13. package/build/components/Combobox/index.js +3 -0
  14. package/build/components/DatePicker/TimePicker.d.ts +3 -0
  15. package/build/components/DatePicker/TimePicker.js +84 -0
  16. package/build/components/DatePicker/time-picker/animated-math.d.ts +4 -0
  17. package/build/components/DatePicker/time-picker/animated-math.js +19 -0
  18. package/build/components/DatePicker/time-picker/period-native.d.ts +6 -0
  19. package/build/components/DatePicker/time-picker/period-native.js +17 -0
  20. package/build/components/DatePicker/time-picker/period-picker.d.ts +6 -0
  21. package/build/components/DatePicker/time-picker/period-picker.js +10 -0
  22. package/build/components/DatePicker/time-picker/period-web.d.ts +6 -0
  23. package/build/components/DatePicker/time-picker/period-web.js +21 -0
  24. package/build/components/DatePicker/time-picker/wheel-native.d.ts +8 -0
  25. package/build/components/DatePicker/time-picker/wheel-native.js +19 -0
  26. package/build/components/DatePicker/time-picker/wheel-picker/index.d.ts +2 -0
  27. package/build/components/DatePicker/time-picker/wheel-picker/index.js +2 -0
  28. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.d.ts +16 -0
  29. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.js +97 -0
  30. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.d.ts +21 -0
  31. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.js +88 -0
  32. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.d.ts +23 -0
  33. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.js +21 -0
  34. package/build/components/DatePicker/time-picker/wheel-web.d.ts +8 -0
  35. package/build/components/DatePicker/time-picker/wheel-web.js +146 -0
  36. package/build/components/DatePicker/time-picker/wheel.d.ts +8 -0
  37. package/build/components/DatePicker/time-picker/wheel.js +10 -0
  38. package/build/components/Modal/Modal.js +26 -42
  39. package/build/components/Modal/Modal.web.js +3 -3
  40. package/build/components/Pagination/Pagination.d.ts +6 -0
  41. package/build/components/Pagination/Pagination.js +125 -0
  42. package/build/components/Pagination/Pagination.props.d.ts +26 -0
  43. package/build/components/Pagination/Pagination.props.js +1 -0
  44. package/build/components/Pagination/Pagination.utils.d.ts +2 -0
  45. package/build/components/Pagination/Pagination.utils.js +20 -0
  46. package/build/components/Pagination/Pagination.utils.test.d.ts +1 -0
  47. package/build/components/Pagination/Pagination.utils.test.js +16 -0
  48. package/build/components/Pagination/index.d.ts +2 -0
  49. package/build/components/Pagination/index.js +1 -0
  50. package/build/components/SafeAreaView/SafeAreaView.d.ts +5 -0
  51. package/build/components/SafeAreaView/SafeAreaView.js +117 -0
  52. package/build/components/SafeAreaView/SafeAreaView.props.d.ts +17 -0
  53. package/build/components/SafeAreaView/SafeAreaView.props.js +1 -0
  54. package/build/components/SafeAreaView/index.d.ts +2 -0
  55. package/build/components/SafeAreaView/index.js +1 -0
  56. package/build/components/Select/Select.d.ts +1 -1
  57. package/build/components/Select/Select.js +6 -5
  58. package/build/components/Select/Select.props.d.ts +4 -0
  59. package/build/components/Select/SelectOption.d.ts +1 -1
  60. package/build/components/Select/SelectOption.js +2 -2
  61. package/build/components/Table/Table.context.d.ts +12 -0
  62. package/build/components/Table/Table.context.js +9 -0
  63. package/build/components/Table/Table.d.ts +6 -0
  64. package/build/components/Table/Table.js +71 -0
  65. package/build/components/Table/Table.props.d.ts +56 -0
  66. package/build/components/Table/Table.props.js +1 -0
  67. package/build/components/Table/Table.utils.d.ts +5 -0
  68. package/build/components/Table/Table.utils.js +48 -0
  69. package/build/components/Table/Table.utils.test.d.ts +1 -0
  70. package/build/components/Table/Table.utils.test.js +71 -0
  71. package/build/components/Table/TableBody.d.ts +6 -0
  72. package/build/components/Table/TableBody.js +16 -0
  73. package/build/components/Table/TableCell.d.ts +10 -0
  74. package/build/components/Table/TableCell.js +44 -0
  75. package/build/components/Table/TableHeader.d.ts +6 -0
  76. package/build/components/Table/TableHeader.js +24 -0
  77. package/build/components/Table/TableHeaderCell.d.ts +10 -0
  78. package/build/components/Table/TableHeaderCell.js +97 -0
  79. package/build/components/Table/TablePagination.d.ts +6 -0
  80. package/build/components/Table/TablePagination.js +7 -0
  81. package/build/components/Table/TableRow.d.ts +8 -0
  82. package/build/components/Table/TableRow.js +25 -0
  83. package/build/components/Table/index.d.ts +8 -0
  84. package/build/components/Table/index.js +7 -0
  85. package/build/components/Timeline/Timeline.d.ts +6 -0
  86. package/build/components/Timeline/Timeline.js +34 -0
  87. package/build/components/Timeline/Timeline.props.d.ts +47 -0
  88. package/build/components/Timeline/Timeline.props.js +1 -0
  89. package/build/components/Timeline/TimelineItem.d.ts +6 -0
  90. package/build/components/Timeline/TimelineItem.js +235 -0
  91. package/build/components/Timeline/index.d.ts +3 -0
  92. package/build/components/Timeline/index.js +2 -0
  93. package/build/components/VerificationInput/VerificationInput.js +3 -3
  94. package/build/components/index.d.ts +5 -0
  95. package/build/components/index.js +5 -0
  96. package/build/tokens/components/dark/timeline.d.ts +2 -2
  97. package/build/tokens/components/dark/timeline.js +2 -2
  98. package/docs/components/AllComponents.web.tsx +106 -23
  99. package/docs/llm-docs/unistyles-llms-full.txt +1132 -534
  100. package/docs/llm-docs/unistyles-llms-small.txt +37 -37
  101. package/package.json +4 -4
  102. package/src/components/Combobox/Combobox.context.ts +26 -0
  103. package/src/components/Combobox/Combobox.docs.mdx +277 -0
  104. package/src/components/Combobox/Combobox.figma.tsx +60 -0
  105. package/src/components/Combobox/Combobox.props.ts +187 -0
  106. package/src/components/Combobox/Combobox.stories.tsx +233 -0
  107. package/src/components/Combobox/Combobox.tsx +446 -0
  108. package/src/components/Combobox/ComboboxOption.tsx +100 -0
  109. package/src/components/Combobox/index.ts +9 -0
  110. package/src/components/Modal/Modal.tsx +52 -74
  111. package/src/components/Modal/Modal.web.tsx +3 -3
  112. package/src/components/Pagination/Pagination.docs.mdx +99 -0
  113. package/src/components/Pagination/Pagination.figma.tsx +20 -0
  114. package/src/components/Pagination/Pagination.props.ts +28 -0
  115. package/src/components/Pagination/Pagination.stories.tsx +88 -0
  116. package/src/components/Pagination/Pagination.tsx +248 -0
  117. package/src/components/Pagination/Pagination.utils.test.ts +20 -0
  118. package/src/components/Pagination/Pagination.utils.ts +37 -0
  119. package/src/components/Pagination/index.ts +2 -0
  120. package/src/components/SafeAreaView/SafeAreaView.props.ts +20 -0
  121. package/src/components/SafeAreaView/SafeAreaView.tsx +173 -0
  122. package/src/components/SafeAreaView/index.ts +2 -0
  123. package/src/components/Select/Select.props.ts +4 -0
  124. package/src/components/Select/Select.tsx +35 -28
  125. package/src/components/Select/SelectOption.tsx +2 -0
  126. package/src/components/Table/Table.context.tsx +23 -0
  127. package/src/components/Table/Table.docs.mdx +239 -0
  128. package/src/components/Table/Table.figma.tsx +65 -0
  129. package/src/components/Table/Table.props.ts +65 -0
  130. package/src/components/Table/Table.stories.tsx +399 -0
  131. package/src/components/Table/Table.tsx +127 -0
  132. package/src/components/Table/Table.utils.test.ts +82 -0
  133. package/src/components/Table/Table.utils.ts +72 -0
  134. package/src/components/Table/TableBody.tsx +25 -0
  135. package/src/components/Table/TableCell.tsx +67 -0
  136. package/src/components/Table/TableHeader.tsx +41 -0
  137. package/src/components/Table/TableHeaderCell.tsx +136 -0
  138. package/src/components/Table/TablePagination.tsx +10 -0
  139. package/src/components/Table/TableRow.tsx +42 -0
  140. package/src/components/Table/index.ts +16 -0
  141. package/src/components/Timeline/Timeline.docs.mdx +177 -0
  142. package/src/components/Timeline/Timeline.figma.tsx +89 -0
  143. package/src/components/Timeline/Timeline.props.ts +51 -0
  144. package/src/components/Timeline/Timeline.stories.tsx +102 -0
  145. package/src/components/Timeline/Timeline.tsx +48 -0
  146. package/src/components/Timeline/TimelineItem.tsx +293 -0
  147. package/src/components/Timeline/index.ts +9 -0
  148. package/src/components/VerificationInput/VerificationInput.tsx +3 -0
  149. package/src/components/index.ts +5 -0
  150. package/src/tokens/components/dark/timeline.ts +2 -2
@@ -0,0 +1,88 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { memo, useEffect, useMemo, useRef, useState } from 'react';
3
+ import { Animated, Platform, View, } from 'react-native';
4
+ import WheelPickerItem from './wheel-picker-item';
5
+ import styles from './wheel-picker.style';
6
+ const WheelPicker = ({ value, options, onChange, selectedIndicatorStyle = {}, containerStyle = {}, itemStyle = {}, itemHeight = 40, scaleFunction = (x) => 1.0 ** x, rotationFunction = (x) => 1 - Math.pow(1 / 2, x), opacityFunction = (x) => Math.pow(1 / 3, x), visibleRest = 2, decelerationRate = 'normal', containerProps = {}, flatListProps = {}, }) => {
7
+ const momentumStarted = useRef(false);
8
+ const selectedIndex = options.findIndex(item => item.value === value);
9
+ const flatListRef = useRef(null);
10
+ const [scrollY] = useState(new Animated.Value(selectedIndex * itemHeight));
11
+ const containerHeight = (1 + visibleRest * 2) * itemHeight;
12
+ const paddedOptions = useMemo(() => {
13
+ const array = [...options];
14
+ for (let i = 0; i < visibleRest; i++) {
15
+ array.unshift(null);
16
+ array.push(null);
17
+ }
18
+ return array;
19
+ }, [options, visibleRest]);
20
+ const offsets = useMemo(() => [...Array(paddedOptions.length)].map((_, i) => i * itemHeight), [paddedOptions, itemHeight]);
21
+ const currentScrollIndex = useMemo(() => Animated.add(Animated.divide(scrollY, itemHeight), visibleRest), [visibleRest, scrollY, itemHeight]);
22
+ const handleScrollEnd = (event) => {
23
+ const offsetY = Math.min(itemHeight * (options.length - 1), Math.max(event.nativeEvent.contentOffset.y, 0));
24
+ let index = Math.floor(offsetY / itemHeight);
25
+ const remainder = offsetY % itemHeight;
26
+ if (remainder > itemHeight / 2) {
27
+ index++;
28
+ }
29
+ if (index !== selectedIndex) {
30
+ onChange(options[index]?.value || 0);
31
+ }
32
+ };
33
+ const handleMomentumScrollBegin = () => {
34
+ momentumStarted.current = true;
35
+ };
36
+ const handleMomentumScrollEnd = (event) => {
37
+ momentumStarted.current = false;
38
+ handleScrollEnd(event);
39
+ };
40
+ const handleScrollEndDrag = (event) => {
41
+ // Capture the offset value immediately
42
+ const offsetY = event.nativeEvent.contentOffset?.y;
43
+ // We'll start a short timer to see if momentum scroll begins
44
+ setTimeout(() => {
45
+ // If momentum scroll hasn't started within the timeout,
46
+ // then it was a slow scroll that won't trigger momentum
47
+ if (!momentumStarted.current && offsetY !== undefined) {
48
+ // Create a synthetic event with just the data we need
49
+ const syntheticEvent = {
50
+ nativeEvent: {
51
+ contentOffset: { y: offsetY },
52
+ },
53
+ };
54
+ handleScrollEnd(syntheticEvent);
55
+ }
56
+ }, 50);
57
+ };
58
+ useEffect(() => {
59
+ if (selectedIndex < 0 || selectedIndex >= options.length) {
60
+ throw new Error(`Selected index ${selectedIndex} is out of bounds [0, ${options.length - 1}]`);
61
+ }
62
+ }, [selectedIndex, options]);
63
+ /**
64
+ * If selectedIndex is changed from outside (not via onChange) we need to scroll to the specified index.
65
+ * This ensures that what the user sees as selected in the picker always corresponds to the value state.
66
+ */
67
+ useEffect(() => {
68
+ flatListRef.current?.scrollToIndex({
69
+ index: selectedIndex,
70
+ animated: Platform.OS === 'ios',
71
+ });
72
+ }, [selectedIndex, itemHeight]);
73
+ return (_jsxs(View, { style: [styles.container, { height: containerHeight }, containerStyle], ...containerProps, children: [_jsx(View, { style: [
74
+ styles.selectedIndicator,
75
+ selectedIndicatorStyle,
76
+ {
77
+ transform: [{ translateY: -itemHeight / 2 }],
78
+ height: itemHeight,
79
+ },
80
+ ] }), _jsx(Animated.FlatList, { ...flatListProps, ref: flatListRef, nestedScrollEnabled: true, style: styles.scrollView, showsVerticalScrollIndicator: false, onScroll: Animated.event([{ nativeEvent: { contentOffset: { y: scrollY } } }], {
81
+ useNativeDriver: true,
82
+ }), onScrollEndDrag: handleScrollEndDrag, onMomentumScrollBegin: handleMomentumScrollBegin, onMomentumScrollEnd: handleMomentumScrollEnd, snapToOffsets: offsets, decelerationRate: decelerationRate, initialScrollIndex: selectedIndex, getItemLayout: (_, index) => ({
83
+ length: itemHeight,
84
+ offset: itemHeight * index,
85
+ index,
86
+ }), data: paddedOptions, keyExtractor: (item, index) => item ? `${item.value}-${item.text}-${index}` : `null-${index}`, renderItem: ({ item: option, index }) => (_jsx(WheelPickerItem, { index: index, option: option, style: itemStyle, height: itemHeight, currentScrollIndex: currentScrollIndex, scaleFunction: scaleFunction, rotationFunction: rotationFunction, opacityFunction: opacityFunction, visibleRest: visibleRest }, `option-${index}`)) })] }));
87
+ };
88
+ export default memo(WheelPicker);
@@ -0,0 +1,23 @@
1
+ declare const _default: {
2
+ container: {
3
+ position: "relative";
4
+ };
5
+ selectedIndicator: {
6
+ position: "absolute";
7
+ width: "100%";
8
+ top: "50%";
9
+ };
10
+ scrollView: {
11
+ overflow: "hidden";
12
+ flex: number;
13
+ };
14
+ option: {
15
+ alignItems: "center";
16
+ justifyContent: "center";
17
+ paddingHorizontal: number;
18
+ zIndex: number;
19
+ };
20
+ } & {
21
+ useVariants: (variants: never) => void;
22
+ };
23
+ export default _default;
@@ -0,0 +1,21 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+ export default StyleSheet.create({
3
+ container: {
4
+ position: 'relative',
5
+ },
6
+ selectedIndicator: {
7
+ position: 'absolute',
8
+ width: '100%',
9
+ top: '50%',
10
+ },
11
+ scrollView: {
12
+ overflow: 'hidden',
13
+ flex: 1,
14
+ },
15
+ option: {
16
+ alignItems: 'center',
17
+ justifyContent: 'center',
18
+ paddingHorizontal: 16,
19
+ zIndex: 100,
20
+ },
21
+ });
@@ -0,0 +1,8 @@
1
+ import { PickerOption } from '../DatePicker.props';
2
+ interface WheelProps {
3
+ value: number | string;
4
+ setValue?: (value: any) => void;
5
+ items: PickerOption[];
6
+ }
7
+ declare const _default: import("react").MemoExoticComponent<({ value, setValue, items }: WheelProps) => import("react/jsx-runtime").JSX.Element>;
8
+ export default _default;
@@ -0,0 +1,146 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { memo, useMemo, useRef } from 'react';
3
+ import { Animated, PanResponder, Platform, View } from 'react-native';
4
+ import { StyleSheet } from 'react-native-unistyles';
5
+ import { isEqual } from '../../../utils';
6
+ import { BodyText } from '../../BodyText';
7
+ import { CONTAINER_HEIGHT } from '../enums';
8
+ import { sin } from './animated-math';
9
+ const ITEM_HEIGHT = 44;
10
+ const WheelWeb = ({ value, setValue = () => { }, items }) => {
11
+ const displayCount = 5;
12
+ const translateY = useRef(new Animated.Value(0)).current;
13
+ const renderCount = displayCount * 2 < items.length ? displayCount * 8 : displayCount * 2 - 1;
14
+ const circular = items.length >= displayCount;
15
+ const height = 140;
16
+ const radius = height / 2;
17
+ const valueIndex = useMemo(() => {
18
+ return Math.max(0, items.findIndex(item => item.value === value));
19
+ }, [items, value]);
20
+ const panResponder = useMemo(() => {
21
+ return PanResponder.create({
22
+ onMoveShouldSetPanResponder: () => true,
23
+ onStartShouldSetPanResponderCapture: () => true,
24
+ onPanResponderGrant: () => {
25
+ translateY.setValue(0);
26
+ },
27
+ onPanResponderMove: (evt, gestureState) => {
28
+ translateY.setValue(gestureState.dy);
29
+ evt.stopPropagation();
30
+ },
31
+ onPanResponderRelease: (_, gestureState) => {
32
+ translateY.extractOffset();
33
+ let newValueIndex = valueIndex - Math.round(gestureState.dy / ((radius * 2) / displayCount));
34
+ if (circular) {
35
+ newValueIndex = (newValueIndex + items.length) % items.length;
36
+ }
37
+ else {
38
+ if (newValueIndex < 0) {
39
+ newValueIndex = 0;
40
+ }
41
+ else if (newValueIndex >= items.length) {
42
+ newValueIndex = items.length - 1;
43
+ }
44
+ }
45
+ const newValue = items[newValueIndex];
46
+ if (newValue?.value === value) {
47
+ translateY.setOffset(0);
48
+ translateY.setValue(0);
49
+ }
50
+ else if (newValue?.value) {
51
+ setValue(newValue.value);
52
+ }
53
+ else if (items[0]?.value) {
54
+ setValue(items[0].value);
55
+ }
56
+ },
57
+ });
58
+ }, [circular, displayCount, radius, setValue, value, valueIndex, items, translateY]);
59
+ const displayValues = useMemo(() => {
60
+ const centerIndex = Math.floor(renderCount / 2);
61
+ return Array.from({ length: renderCount }, (_, index) => {
62
+ let targetIndex = valueIndex + index - centerIndex;
63
+ if (circular) {
64
+ targetIndex = ((targetIndex % items.length) + items.length) % items.length;
65
+ }
66
+ else {
67
+ targetIndex = Math.max(0, Math.min(targetIndex, items.length - 1));
68
+ }
69
+ return items[targetIndex] || items[0];
70
+ });
71
+ }, [renderCount, valueIndex, items, circular]);
72
+ const animatedAngles = useMemo(() => {
73
+ //translateY.setValue(0);
74
+ translateY.setOffset(0);
75
+ const currentIndex = displayValues.findIndex(item => item?.value === value);
76
+ return displayValues && displayValues.length > 0
77
+ ? displayValues.map((_, index) => translateY
78
+ .interpolate({
79
+ inputRange: [-radius, radius],
80
+ outputRange: [
81
+ -radius + ((radius * 2) / displayCount) * (index - currentIndex),
82
+ radius + ((radius * 2) / displayCount) * (index - currentIndex),
83
+ ],
84
+ extrapolate: 'extend',
85
+ })
86
+ .interpolate({
87
+ inputRange: [-radius, radius],
88
+ outputRange: [-Math.PI / 2, Math.PI / 2],
89
+ extrapolate: 'clamp',
90
+ }))
91
+ : [];
92
+ }, [displayValues, radius, value, displayCount, translateY]);
93
+ return (_jsxs(View, { style: [styles.container], ...panResponder.panHandlers, children: [_jsx(View, { style: [
94
+ styles.selectedIndicator,
95
+ {
96
+ transform: [{ translateY: -ITEM_HEIGHT / 2 }],
97
+ height: ITEM_HEIGHT,
98
+ },
99
+ ] }), displayValues?.map((displayValue, index) => {
100
+ const animatedAngle = animatedAngles[index];
101
+ return (_jsx(Animated.View, { style: {
102
+ position: 'absolute',
103
+ height: ITEM_HEIGHT - 10,
104
+ transform: animatedAngle
105
+ ? [
106
+ {
107
+ translateY: Animated.multiply(radius, sin(animatedAngle)),
108
+ },
109
+ {
110
+ rotateX: animatedAngle.interpolate({
111
+ inputRange: [-Math.PI / 2, Math.PI / 2],
112
+ outputRange: ['-89deg', '89deg'],
113
+ extrapolate: 'clamp',
114
+ }),
115
+ },
116
+ ]
117
+ : [],
118
+ opacity: displayValue?.value !== value ? 0.3 : 1,
119
+ }, children: _jsx(BodyText, { children: displayValue?.text }) }, `${displayValue?.text}-${index}`));
120
+ })] }));
121
+ };
122
+ const styles = StyleSheet.create({
123
+ container: {
124
+ minWidth: 30,
125
+ overflow: 'hidden',
126
+ alignItems: 'center',
127
+ justifyContent: 'center',
128
+ height: CONTAINER_HEIGHT / 2,
129
+ ...Platform.select({
130
+ web: {
131
+ cursor: 'pointer',
132
+ userSelect: 'none',
133
+ },
134
+ }),
135
+ },
136
+ selectedIndicator: {
137
+ position: 'absolute',
138
+ width: '100%',
139
+ top: '50%',
140
+ },
141
+ });
142
+ const customComparator = (prev, next) => {
143
+ const areEqual = prev.value === next.value && prev.setValue === next.setValue && isEqual(prev.items, next.items);
144
+ return areEqual;
145
+ };
146
+ export default memo(WheelWeb, customComparator);
@@ -0,0 +1,8 @@
1
+ import { PickerOption } from '../DatePicker.props';
2
+ type WheelProps = {
3
+ value: number | string;
4
+ setValue?: (value: any) => void;
5
+ items: PickerOption[];
6
+ };
7
+ declare const _default: import("react").MemoExoticComponent<(props: WheelProps) => import("react/jsx-runtime").JSX.Element>;
8
+ export default _default;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { memo } from 'react';
3
+ import { Platform } from 'react-native';
4
+ import WheelNative from './wheel-native';
5
+ import WheelWeb from './wheel-web';
6
+ const Wheel = (props) => {
7
+ const Component = Platform.OS === 'web' ? WheelWeb : WheelNative;
8
+ return _jsx(Component, { ...props });
9
+ };
10
+ export default memo(Wheel);
@@ -1,8 +1,8 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { BottomSheetFooter, } from '@gorhom/bottom-sheet';
3
3
  import { CloseMediumIcon } from '@utilitywarehouse/hearth-react-native-icons';
4
- import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react';
5
- import { AccessibilityInfo, Dimensions, Platform, ScrollView, View, findNodeHandle, } from 'react-native';
4
+ import { useCallback, useEffect, useImperativeHandle, useMemo, useRef } from 'react';
5
+ import { AccessibilityInfo, Platform, ScrollView, View, findNodeHandle } from 'react-native';
6
6
  import Animated, { Easing, useAnimatedStyle, useSharedValue, withDelay, withTiming, } from 'react-native-reanimated';
7
7
  import { StyleSheet } from 'react-native-unistyles';
8
8
  import { useTheme } from '../../hooks';
@@ -11,6 +11,7 @@ import { BodyText } from '../BodyText';
11
11
  import { BottomSheetModal, BottomSheetScrollView } from '../BottomSheet';
12
12
  import { Button } from '../Button';
13
13
  import { Heading } from '../Heading';
14
+ import { SafeAreaView } from '../SafeAreaView';
14
15
  import { Spinner } from '../Spinner';
15
16
  import { UnstyledIconButton } from '../UnstyledIconButton';
16
17
  const Modal = ({ ref, children, heading, description, showCloseButton = true, primaryButtonText, secondaryButtonText, onPressPrimaryButton, onPressCloseButton, onPressSecondaryButton, closeOnPrimaryButtonPress = true, closeOnSecondaryButtonPress = true, loading, loadingHeading = 'Loading...', fullscreen = false, image, primaryButtonProps, secondaryButtonProps, closeButtonProps, inNavModal = false, stickyFooter = true, background = 'default', scrollable = true, ...props }) => {
@@ -21,13 +22,6 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
21
22
  const backgroundOpacity = useSharedValue(0);
22
23
  const pretendContentTranslateY = useSharedValue(20);
23
24
  const isBrandBackground = background === 'brand';
24
- const [inNavModalHeight, setInNavModalHeight] = useState();
25
- const isNavModalFullScreen = useMemo(() => {
26
- if (!inNavModalHeight || !inNavModal)
27
- return false;
28
- const screenHeight = Dimensions.get('window').height;
29
- return inNavModalHeight >= screenHeight;
30
- }, [inNavModalHeight, inNavModal]);
31
25
  const triggerCloseAnimation = useCallback(() => {
32
26
  if (Platform.OS === 'android' && inNavModal) {
33
27
  pretendContentTranslateY.value = withTiming(20, {
@@ -39,7 +33,7 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
39
33
  easing: Easing.in(Easing.quad),
40
34
  });
41
35
  }
42
- }, [Platform.OS, inNavModal, pretendContentTranslateY, backgroundOpacity]);
36
+ }, [inNavModal, pretendContentTranslateY, backgroundOpacity]);
43
37
  useImperativeHandle(ref, () => ({
44
38
  ...bottomSheetModalRef.current,
45
39
  triggerCloseAnimation,
@@ -84,28 +78,28 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
84
78
  }
85
79
  props.onChange?.(index, position, type);
86
80
  };
87
- const handleCloseButtonPress = () => {
81
+ const handleCloseButtonPress = useCallback(() => {
88
82
  bottomSheetModalRef.current?.dismiss();
89
83
  if (onPressCloseButton) {
90
84
  onPressCloseButton();
91
85
  }
92
- };
93
- const handlePrimaryButtonPress = () => {
86
+ }, [onPressCloseButton]);
87
+ const handlePrimaryButtonPress = useCallback(() => {
94
88
  if (onPressPrimaryButton) {
95
89
  onPressPrimaryButton();
96
90
  }
97
91
  if (closeOnPrimaryButtonPress) {
98
92
  bottomSheetModalRef.current?.dismiss();
99
93
  }
100
- };
101
- const handleSecondaryButtonPress = () => {
94
+ }, [closeOnPrimaryButtonPress, onPressPrimaryButton]);
95
+ const handleSecondaryButtonPress = useCallback(() => {
102
96
  if (onPressSecondaryButton) {
103
97
  onPressSecondaryButton();
104
98
  }
105
99
  if (closeOnSecondaryButtonPress) {
106
100
  bottomSheetModalRef.current?.dismiss();
107
101
  }
108
- };
102
+ }, [closeOnSecondaryButtonPress, onPressSecondaryButton]);
109
103
  const noButtons = !onPressPrimaryButton && !onPressSecondaryButton;
110
104
  styles.useVariants({
111
105
  loading,
@@ -114,11 +108,19 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
114
108
  stickyFooter,
115
109
  showHandle: props.showHandle,
116
110
  background: isBrandBackground ? 'brand' : 'primary',
117
- ...(inNavModal && {
118
- fullscreen: isNavModalFullScreen,
119
- }),
120
111
  });
121
- const footer = (_jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, inverted: isBrandBackground && inNavModal, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, inverted: isBrandBackground && inNavModal, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] }));
112
+ const footer = useMemo(() => (_jsxs(View, { style: styles.footer, children: [onPressPrimaryButton && primaryButtonText ? (_jsx(Button, { onPress: handlePrimaryButtonPress, text: primaryButtonText, inverted: isBrandBackground && inNavModal, ...primaryButtonProps, variant: primaryButtonProps?.variant ?? 'solid', colorScheme: primaryButtonProps?.colorScheme ?? 'highlight' })) : null, onPressSecondaryButton && secondaryButtonText ? (_jsx(Button, { onPress: handleSecondaryButtonPress, text: secondaryButtonText, inverted: isBrandBackground && inNavModal, ...secondaryButtonProps, variant: secondaryButtonProps?.variant ?? 'outline', colorScheme: secondaryButtonProps?.colorScheme ?? 'functional' })) : null] })), [
113
+ handlePrimaryButtonPress,
114
+ handleSecondaryButtonPress,
115
+ inNavModal,
116
+ isBrandBackground,
117
+ onPressPrimaryButton,
118
+ onPressSecondaryButton,
119
+ primaryButtonProps,
120
+ primaryButtonText,
121
+ secondaryButtonProps,
122
+ secondaryButtonText,
123
+ ]);
122
124
  const InNavModalContainer = scrollable ? ScrollView : View;
123
125
  const content = (_jsx(_Fragment, { children: loading ? (_jsxs(View, { style: styles.loadingContainer, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Loading' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsx(Spinner, { size: "lg", color: isBrandBackground && inNavModal ? theme.color.icon.inverted : undefined }), _jsx(Heading, { size: "lg", textAlign: "center", inverted: isBrandBackground && inNavModal, children: loadingHeading })] })) : (_jsxs(View, { style: styles.container, accessible: Platform.OS === 'android' ? true : undefined, accessibilityLabel: Platform.OS === 'android' ? 'Modal content' : undefined, screenReaderFocusable: true, ref: viewRef, children: [_jsxs(View, { style: styles.header, children: [_jsxs(View, { style: styles.headerTextContent, children: [heading && !image ? (_jsx(Heading, { size: "lg", accessible: true, inverted: isBrandBackground && inNavModal, children: heading })) : null, description && !image ? (_jsx(BodyText, { accessible: true, inverted: isBrandBackground && inNavModal, children: description })) : null] }), showCloseButton ? (_jsx(UnstyledIconButton, { icon: CloseMediumIcon, onPress: handleCloseButtonPress, accessibilityLabel: "Close modal", inverted: isBrandBackground && inNavModal, ...closeButtonProps })) : null] }), image ? (_jsxs(View, { style: styles.imageContainer, children: [image, _jsxs(View, { style: styles.textContent, children: [heading ? (_jsx(Heading, { size: "lg", textAlign: "center", accessible: true, inverted: isBrandBackground && inNavModal, children: heading })) : null, description ? (_jsx(BodyText, { textAlign: "center", accessible: true, inverted: isBrandBackground && inNavModal, children: description })) : null] })] })) : null, inNavModal && (_jsxs(InNavModalContainer, { style: {
124
126
  flex: stickyFooter ? 1 : 0,
@@ -126,20 +128,11 @@ const Modal = ({ ref, children, heading, description, showCloseButton = true, pr
126
128
  }, ...(scrollable ? { contentContainerStyle: { paddingHorizontal: 1 } } : {}), children: [children, !stickyFooter ? (_jsx(View, { style: styles.inNavModalFooterContainer, children: footer })) : null] })), !inNavModal && children, ((!stickyFooter && !inNavModal) || (inNavModal && stickyFooter)) && !noButtons
127
129
  ? footer
128
130
  : null] })) }));
129
- const renderFooter = useCallback((props) => (_jsx(BottomSheetFooter, { ...props, children: _jsx(View, { style: styles.footerWrap, children: footer }) })), [
130
- onPressPrimaryButton,
131
- primaryButtonText,
132
- onPressSecondaryButton,
133
- secondaryButtonText,
134
- primaryButtonProps,
135
- secondaryButtonProps,
136
- ]);
137
- return inNavModal ? (_jsxs(View, { onLayout: e => {
138
- setInNavModalHeight(e.nativeEvent.layout.height);
139
- }, style: {
131
+ const renderFooter = useCallback((props) => (_jsx(BottomSheetFooter, { ...props, children: _jsx(View, { style: styles.footerWrap, children: footer }) })), [footer]);
132
+ return inNavModal ? (_jsxs(View, { style: {
140
133
  flex: 1,
141
134
  backgroundColor: theme.color.background[isBrandBackground ? 'brand' : 'primary'],
142
- }, children: [Platform.OS === 'android' ? (_jsx(Animated.View, { style: [styles.androidContainer, animatedBackgroundStyle], children: _jsx(Animated.View, { style: [styles.pretendContent, animatedPretendContentStyle] }) })) : null, _jsx(Animated.View, { style: [styles.inNavModalContainer, Platform.OS === 'android' && animatedInNavModalStyle], children: _jsx(View, { style: styles.inNavModalContent, children: content }) })] })) : (_jsxs(BottomSheetModal, { ref: bottomSheetModalRef, enableDynamicSizing: true, snapPoints: image || fullscreen ? ['90%'] : props.snapPoints, showHandle: typeof loading !== 'undefined' && loading ? false : props.showHandle, accessible: false, style: styles.modal, footerComponent: stickyFooter && !noButtons ? renderFooter : undefined, ...props, onChange: handleChange, children: [loading ? _jsx(View, { style: styles.loadingTop }) : null, _jsx(BottomSheetScrollView, { contentContainerStyle: styles.scrollView, ref: scrollViewRef, children: content })] }));
135
+ }, children: [Platform.OS === 'android' ? (_jsx(Animated.View, { style: [styles.androidContainer, animatedBackgroundStyle], children: _jsx(Animated.View, { style: [styles.pretendContent, animatedPretendContentStyle] }) })) : null, _jsx(Animated.View, { style: [styles.inNavModalContainer, Platform.OS === 'android' && animatedInNavModalStyle], children: _jsx(SafeAreaView, { edges: ['top', 'bottom'], style: styles.inNavModalContent, children: content }) })] })) : (_jsxs(BottomSheetModal, { ref: bottomSheetModalRef, enableDynamicSizing: true, snapPoints: image || fullscreen ? ['90%'] : props.snapPoints, showHandle: typeof loading !== 'undefined' && loading ? false : props.showHandle, accessible: false, style: styles.modal, footerComponent: stickyFooter && !noButtons ? renderFooter : undefined, ...props, onChange: handleChange, children: [loading ? _jsx(View, { style: styles.loadingTop }) : null, _jsx(BottomSheetScrollView, { contentContainerStyle: styles.scrollView, ref: scrollViewRef, children: content })] }));
143
136
  };
144
137
  const styles = StyleSheet.create((theme, rt) => ({
145
138
  modal: {
@@ -245,7 +238,7 @@ const styles = StyleSheet.create((theme, rt) => ({
245
238
  borderTopLeftRadius: theme.components.modal.borderRadius,
246
239
  borderTopRightRadius: theme.components.modal.borderRadius,
247
240
  backgroundColor: theme.color.surface.neutral.strong,
248
- paddingBottom: theme.components.bottomSheet.padding + rt.insets.bottom,
241
+ padding: theme.components.bottomSheet.padding,
249
242
  variants: {
250
243
  background: {
251
244
  primary: {},
@@ -253,15 +246,6 @@ const styles = StyleSheet.create((theme, rt) => ({
253
246
  backgroundColor: theme.color.background.brand,
254
247
  },
255
248
  },
256
- fullscreen: {
257
- true: {
258
- padding: theme.components.bottomSheet.padding,
259
- paddingTop: rt.insets.top,
260
- },
261
- false: {
262
- padding: theme.components.bottomSheet.padding,
263
- },
264
- },
265
249
  },
266
250
  },
267
251
  inNavModalFooterContainer: {
@@ -141,13 +141,13 @@ const styles = StyleSheet.create((theme, rt) => ({
141
141
  borderTopRightRadius: theme.components.modal.borderRadius,
142
142
  backgroundColor: theme.color.surface.neutral.strong,
143
143
  gap: theme.components.modal.gap,
144
- padding: theme.components.modal.padding,
144
+ padding: theme.components.bottomSheet.padding,
145
145
  paddingBottom: theme.components.modal.padding + rt.insets.bottom,
146
146
  },
147
147
  androidContainer: {
148
148
  height: rt.insets.top + 18,
149
- paddingLeft: theme.components.modal.padding,
150
- paddingRight: theme.components.modal.padding,
149
+ paddingLeft: theme.components.bottomSheet.padding,
150
+ paddingRight: theme.components.bottomSheet.padding,
151
151
  justifyContent: 'flex-end',
152
152
  },
153
153
  pretendContent: {
@@ -0,0 +1,6 @@
1
+ import type PaginationProps from './Pagination.props';
2
+ declare const Pagination: {
3
+ ({ currentPage, totalPages, onPageChange, condensed, hideSkipButtons, style, ...props }: PaginationProps): import("react/jsx-runtime").JSX.Element;
4
+ displayName: string;
5
+ };
6
+ export default Pagination;
@@ -0,0 +1,125 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { ChevronLeftSmallIcon, ChevronRightSmallIcon, SkipFirstSmallIcon, SkipLastSmallIcon, } from '@utilitywarehouse/hearth-react-native-icons';
3
+ import { useState } from 'react';
4
+ import { Pressable, View } from 'react-native';
5
+ import { StyleSheet } from 'react-native-unistyles';
6
+ import { BodyText } from '../BodyText';
7
+ import { UnstyledIconButton } from '../UnstyledIconButton';
8
+ import { ELLIPSIS, generatePageNumbers } from './Pagination.utils';
9
+ const PaginationItem = ({ label, selected = false, onPress }) => {
10
+ const [isFocused, setIsFocused] = useState(false);
11
+ styles.useVariants({ selected });
12
+ return (_jsx(Pressable, { accessibilityRole: "button", accessibilityLabel: `Go to page ${label}`, accessibilityState: { selected }, onBlur: () => setIsFocused(false), onFocus: () => setIsFocused(true), onPress: onPress, style: ({ pressed }) => [
13
+ styles.pageItem,
14
+ pressed && !selected && styles.pageItemPressed,
15
+ isFocused && styles.pageItemFocused,
16
+ ], children: _jsx(BodyText, { size: "md", style: styles.pageItemText, children: label }) }));
17
+ };
18
+ const PaginationArrowButton = ({ accessibilityLabel, disabled, icon, onPress, }) => {
19
+ return (_jsx(UnstyledIconButton, { accessibilityLabel: accessibilityLabel, disabled: disabled, icon: icon, onPress: onPress, size: "sm", style: styles.arrowButton }));
20
+ };
21
+ const Pagination = ({ currentPage, totalPages, onPageChange, condensed = false, hideSkipButtons = false, style, ...props }) => {
22
+ const pages = generatePageNumbers(currentPage, totalPages);
23
+ const handleFirst = () => {
24
+ if (currentPage !== 1) {
25
+ onPageChange(1);
26
+ }
27
+ };
28
+ const handlePrevious = () => {
29
+ if (currentPage > 1) {
30
+ onPageChange(currentPage - 1);
31
+ }
32
+ };
33
+ const handleNext = () => {
34
+ if (currentPage < totalPages) {
35
+ onPageChange(currentPage + 1);
36
+ }
37
+ };
38
+ const handleLast = () => {
39
+ if (currentPage !== totalPages) {
40
+ onPageChange(totalPages);
41
+ }
42
+ };
43
+ return (_jsxs(View, { accessibilityLabel: "Pagination", style: [styles.container, style], ...props, children: [_jsxs(View, { style: styles.controllers, children: [!hideSkipButtons ? (_jsx(PaginationArrowButton, { accessibilityLabel: "Go to first page", disabled: currentPage === 1, icon: SkipFirstSmallIcon, onPress: handleFirst })) : null, _jsx(PaginationArrowButton, { accessibilityLabel: "Go to previous page", disabled: currentPage === 1, icon: ChevronLeftSmallIcon, onPress: handlePrevious })] }), condensed ? (_jsxs(BodyText, { size: "md", children: ["Page ", currentPage, " of ", totalPages] })) : (_jsx(View, { style: styles.pages, children: pages.map((page, index) => {
44
+ if (page === ELLIPSIS) {
45
+ return (_jsx(BodyText, { size: "md", style: styles.ellipsis, children: ELLIPSIS }, `ellipsis-${index}`));
46
+ }
47
+ return (_jsx(PaginationItem, { label: page, onPress: () => onPageChange(page), selected: page === currentPage }, page));
48
+ }) })), _jsxs(View, { style: styles.controllers, children: [_jsx(PaginationArrowButton, { accessibilityLabel: "Go to next page", disabled: currentPage === totalPages, icon: ChevronRightSmallIcon, onPress: handleNext }), !hideSkipButtons ? (_jsx(PaginationArrowButton, { accessibilityLabel: "Go to last page", disabled: currentPage === totalPages, icon: SkipLastSmallIcon, onPress: handleLast })) : null] })] }));
49
+ };
50
+ PaginationItem.displayName = 'PaginationItem';
51
+ Pagination.displayName = 'Pagination';
52
+ const styles = StyleSheet.create(theme => ({
53
+ container: {
54
+ width: '100%',
55
+ minHeight: theme.components.pagination.item.height,
56
+ flexDirection: 'row',
57
+ alignItems: 'center',
58
+ justifyContent: 'space-between',
59
+ gap: theme.components.pagination.gap,
60
+ },
61
+ controllers: {
62
+ flexDirection: 'row',
63
+ alignItems: 'center',
64
+ gap: theme.components.pagination.gap,
65
+ },
66
+ arrowButton: {
67
+ width: theme.components.pagination.item.width,
68
+ height: theme.components.pagination.item.height,
69
+ },
70
+ pages: {
71
+ flex: 1,
72
+ flexDirection: 'row',
73
+ alignItems: 'center',
74
+ justifyContent: 'center',
75
+ flexWrap: 'wrap',
76
+ gap: theme.components.pagination.gap,
77
+ },
78
+ pageItem: {
79
+ width: theme.components.pagination.item.width,
80
+ height: theme.components.pagination.item.height,
81
+ borderRadius: theme.components.pagination.item.radius,
82
+ alignItems: 'center',
83
+ justifyContent: 'center',
84
+ backgroundColor: 'transparent',
85
+ _web: {
86
+ _hover: {
87
+ backgroundColor: theme.color.interactive.functional.surface.subtle.hover,
88
+ },
89
+ },
90
+ variants: {
91
+ selected: {
92
+ true: {
93
+ backgroundColor: theme.color.interactive.brand.surface.strong.default,
94
+ },
95
+ },
96
+ },
97
+ },
98
+ pageItemPressed: {
99
+ backgroundColor: theme.color.interactive.functional.surface.subtle.active,
100
+ },
101
+ pageItemFocused: {
102
+ outlineWidth: 2,
103
+ outlineOffset: -2,
104
+ outlineColor: theme.color.border.strong,
105
+ outlineStyle: 'solid',
106
+ },
107
+ pageItemText: {
108
+ variants: {
109
+ selected: {
110
+ true: {
111
+ color: theme.color.text.inverted,
112
+ },
113
+ false: {
114
+ color: theme.color.text.primary,
115
+ },
116
+ },
117
+ },
118
+ },
119
+ ellipsis: {
120
+ minWidth: theme.components.pagination.item.width,
121
+ textAlign: 'center',
122
+ color: theme.color.text.primary,
123
+ },
124
+ }));
125
+ export default Pagination;
@@ -0,0 +1,26 @@
1
+ import type { ViewProps } from 'react-native';
2
+ export interface PaginationProps extends ViewProps {
3
+ /**
4
+ * The current active page number (1-indexed).
5
+ */
6
+ currentPage: number;
7
+ /**
8
+ * The total number of pages.
9
+ */
10
+ totalPages: number;
11
+ /**
12
+ * Callback fired when the page changes.
13
+ */
14
+ onPageChange: (page: number) => void;
15
+ /**
16
+ * Whether to show condensed copy instead of individual page items.
17
+ * @default false
18
+ */
19
+ condensed?: boolean;
20
+ /**
21
+ * Whether to hide the first and last page controls.
22
+ * @default false
23
+ */
24
+ hideSkipButtons?: boolean;
25
+ }
26
+ export default PaginationProps;
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ export declare const ELLIPSIS = "...";
2
+ export declare const generatePageNumbers: (currentPage: number, totalPages: number) => Array<number | typeof ELLIPSIS>;