@utilitywarehouse/hearth-react-native 0.3.1 → 0.4.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 (193) hide show
  1. package/.storybook/preview.tsx +3 -0
  2. package/.turbo/turbo-build.log +1 -1
  3. package/.turbo/turbo-lint.log +1 -1
  4. package/CHANGELOG.md +10 -0
  5. package/build/components/CurrencyInput/CurrencyInput.js +1 -1
  6. package/build/components/DatePicker/DatePicker.context.d.ts +19 -0
  7. package/build/components/DatePicker/DatePicker.context.js +3 -0
  8. package/build/components/DatePicker/DatePicker.d.ts +19 -0
  9. package/build/components/DatePicker/DatePicker.js +479 -0
  10. package/build/components/DatePicker/DatePicker.props.d.ts +125 -0
  11. package/build/components/DatePicker/DatePicker.props.js +1 -0
  12. package/build/components/DatePicker/DatePickerCalendar.d.ts +2 -0
  13. package/build/components/DatePicker/DatePickerCalendar.js +28 -0
  14. package/build/components/DatePicker/DatePickerDay.d.ts +11 -0
  15. package/build/components/DatePicker/DatePickerDay.js +242 -0
  16. package/build/components/DatePicker/DatePickerDays.d.ts +2 -0
  17. package/build/components/DatePicker/DatePickerDays.js +157 -0
  18. package/build/components/DatePicker/DatePickerFooter.d.ts +2 -0
  19. package/build/components/DatePicker/DatePickerFooter.js +23 -0
  20. package/build/components/DatePicker/DatePickerHeader/DatePickerHeader.props.d.ts +8 -0
  21. package/build/components/DatePicker/DatePickerHeader/DatePickerHeader.props.js +1 -0
  22. package/build/components/DatePicker/DatePickerHeader/DatePickerMonthButton.d.ts +2 -0
  23. package/build/components/DatePicker/DatePickerHeader/DatePickerMonthButton.js +14 -0
  24. package/build/components/DatePicker/DatePickerHeader/DatePickerNextButton.d.ts +2 -0
  25. package/build/components/DatePicker/DatePickerHeader/DatePickerNextButton.js +32 -0
  26. package/build/components/DatePicker/DatePickerHeader/DatePickerPrevButton.d.ts +2 -0
  27. package/build/components/DatePicker/DatePickerHeader/DatePickerPrevButton.js +32 -0
  28. package/build/components/DatePicker/DatePickerHeader/DatePickerSelectors.d.ts +6 -0
  29. package/build/components/DatePicker/DatePickerHeader/DatePickerSelectors.js +64 -0
  30. package/build/components/DatePicker/DatePickerHeader/DatePickerTimeButton.d.ts +1 -0
  31. package/build/components/DatePicker/DatePickerHeader/DatePickerTimeButton.js +22 -0
  32. package/build/components/DatePicker/DatePickerHeader/DatePickerYearButton.d.ts +2 -0
  33. package/build/components/DatePicker/DatePickerHeader/DatePickerYearButton.js +25 -0
  34. package/build/components/DatePicker/DatePickerHeader/index.d.ts +3 -0
  35. package/build/components/DatePicker/DatePickerHeader/index.js +30 -0
  36. package/build/components/DatePicker/DatePickerMonths.d.ts +2 -0
  37. package/build/components/DatePicker/DatePickerMonths.js +69 -0
  38. package/build/components/DatePicker/DatePickerWeekdays.d.ts +8 -0
  39. package/build/components/DatePicker/DatePickerWeekdays.js +26 -0
  40. package/build/components/DatePicker/DatePickerYears.d.ts +2 -0
  41. package/build/components/DatePicker/DatePickerYears.js +83 -0
  42. package/build/components/DatePicker/TimePicker.d.ts +3 -0
  43. package/build/components/DatePicker/TimePicker.js +84 -0
  44. package/build/components/DatePicker/enums.d.ts +12 -0
  45. package/build/components/DatePicker/enums.js +12 -0
  46. package/build/components/DatePicker/index.d.ts +4 -0
  47. package/build/components/DatePicker/index.js +3 -0
  48. package/build/components/DatePicker/polyfill.d.ts +0 -0
  49. package/build/components/DatePicker/polyfill.js +22 -0
  50. package/build/components/DatePicker/time-picker/animated-math.d.ts +4 -0
  51. package/build/components/DatePicker/time-picker/animated-math.js +19 -0
  52. package/build/components/DatePicker/time-picker/period-native.d.ts +6 -0
  53. package/build/components/DatePicker/time-picker/period-native.js +17 -0
  54. package/build/components/DatePicker/time-picker/period-picker.d.ts +6 -0
  55. package/build/components/DatePicker/time-picker/period-picker.js +10 -0
  56. package/build/components/DatePicker/time-picker/period-web.d.ts +6 -0
  57. package/build/components/DatePicker/time-picker/period-web.js +21 -0
  58. package/build/components/DatePicker/time-picker/wheel-native.d.ts +8 -0
  59. package/build/components/DatePicker/time-picker/wheel-native.js +19 -0
  60. package/build/components/DatePicker/time-picker/wheel-picker/index.d.ts +2 -0
  61. package/build/components/DatePicker/time-picker/wheel-picker/index.js +2 -0
  62. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.d.ts +16 -0
  63. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.js +97 -0
  64. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.d.ts +21 -0
  65. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.js +88 -0
  66. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.d.ts +23 -0
  67. package/build/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.js +21 -0
  68. package/build/components/DatePicker/time-picker/wheel-web.d.ts +8 -0
  69. package/build/components/DatePicker/time-picker/wheel-web.js +148 -0
  70. package/build/components/DatePicker/time-picker/wheel.d.ts +8 -0
  71. package/build/components/DatePicker/time-picker/wheel.js +10 -0
  72. package/build/components/DatePicker/utils.d.ts +212 -0
  73. package/build/components/DatePicker/utils.js +391 -0
  74. package/build/components/DatePickerInput/DatePickerInput.d.ts +6 -0
  75. package/build/components/DatePickerInput/DatePickerInput.js +126 -0
  76. package/build/components/DatePickerInput/DatePickerInput.props.d.ts +47 -0
  77. package/build/components/DatePickerInput/DatePickerInput.props.js +1 -0
  78. package/build/components/DatePickerInput/DatePickerInputDoneButton.d.ts +8 -0
  79. package/build/components/DatePickerInput/DatePickerInputDoneButton.js +19 -0
  80. package/build/components/DatePickerInput/DatePickerInputDoneButton.web.d.ts +5 -0
  81. package/build/components/DatePickerInput/DatePickerInputDoneButton.web.js +5 -0
  82. package/build/components/DatePickerInput/index.d.ts +2 -0
  83. package/build/components/DatePickerInput/index.js +1 -0
  84. package/build/components/Input/InputField.d.ts +1 -1
  85. package/build/components/Input/InputField.js +1 -1
  86. package/build/components/Input/InputSlot.d.ts +1 -1
  87. package/build/components/Input/InputSlot.js +3 -3
  88. package/build/components/UnstyledIconButton/UnstyledIconButton.js +2 -2
  89. package/build/components/UnstyledIconButton/UnstyledIconButtonRoot.js +1 -1
  90. package/build/components/index.d.ts +2 -0
  91. package/build/components/index.js +2 -0
  92. package/build/hooks/index.d.ts +4 -3
  93. package/build/hooks/index.js +4 -3
  94. package/build/hooks/usePrevious.d.ts +1 -0
  95. package/build/hooks/usePrevious.js +8 -0
  96. package/build/hooks/useTheme.d.ts +2 -1
  97. package/build/hooks/useTheme.js +2 -2
  98. package/build/tokens/components/dark/date-picker.d.ts +1 -0
  99. package/build/tokens/components/dark/date-picker.js +1 -0
  100. package/build/tokens/components/dark/illustrations.d.ts +7 -0
  101. package/build/tokens/components/dark/illustrations.js +6 -0
  102. package/build/tokens/components/dark/index.d.ts +1 -0
  103. package/build/tokens/components/dark/index.js +1 -0
  104. package/build/tokens/components/dark/segmented-control.d.ts +2 -2
  105. package/build/tokens/components/dark/segmented-control.js +2 -2
  106. package/build/tokens/components/dark/table.d.ts +3 -0
  107. package/build/tokens/components/dark/table.js +3 -0
  108. package/build/tokens/components/light/date-picker.d.ts +1 -0
  109. package/build/tokens/components/light/date-picker.js +1 -0
  110. package/build/tokens/components/light/illustrations.d.ts +7 -0
  111. package/build/tokens/components/light/illustrations.js +6 -0
  112. package/build/tokens/components/light/index.d.ts +1 -0
  113. package/build/tokens/components/light/index.js +1 -0
  114. package/build/tokens/components/light/segmented-control.d.ts +2 -2
  115. package/build/tokens/components/light/segmented-control.js +2 -2
  116. package/build/tokens/components/light/table.d.ts +3 -0
  117. package/build/tokens/components/light/table.js +3 -0
  118. package/build/utils/index.d.ts +1 -0
  119. package/build/utils/index.js +1 -0
  120. package/build/utils/isEqual.d.ts +2 -0
  121. package/build/utils/isEqual.js +29 -0
  122. package/chromatic.config.json +6 -0
  123. package/docs/components/AllComponents.web.tsx +43 -1
  124. package/docs/components/ViewWrap.tsx +32 -0
  125. package/docs/components/index.ts +1 -0
  126. package/docs/getting-started.mdx +6 -6
  127. package/package.json +10 -7
  128. package/src/components/CurrencyInput/CurrencyInput.tsx +2 -1
  129. package/src/components/DatePicker/DatePicker.context.ts +23 -0
  130. package/src/components/DatePicker/DatePicker.docs.mdx +239 -0
  131. package/src/components/DatePicker/DatePicker.props.ts +139 -0
  132. package/src/components/DatePicker/DatePicker.stories.tsx +98 -0
  133. package/src/components/DatePicker/DatePicker.tsx +669 -0
  134. package/src/components/DatePicker/DatePickerCalendar.tsx +41 -0
  135. package/src/components/DatePicker/DatePickerDay.tsx +302 -0
  136. package/src/components/DatePicker/DatePickerDays.tsx +241 -0
  137. package/src/components/DatePicker/DatePickerFooter.tsx +35 -0
  138. package/src/components/DatePicker/DatePickerHeader/DatePickerHeader.props.ts +10 -0
  139. package/src/components/DatePicker/DatePickerHeader/DatePickerMonthButton.tsx +29 -0
  140. package/src/components/DatePicker/DatePickerHeader/DatePickerNextButton.tsx +46 -0
  141. package/src/components/DatePicker/DatePickerHeader/DatePickerPrevButton.tsx +46 -0
  142. package/src/components/DatePicker/DatePickerHeader/DatePickerSelectors.tsx +96 -0
  143. package/src/components/DatePicker/DatePickerHeader/DatePickerTimeButton.tsx +48 -0
  144. package/src/components/DatePicker/DatePickerHeader/DatePickerYearButton.tsx +50 -0
  145. package/src/components/DatePicker/DatePickerHeader/index.tsx +64 -0
  146. package/src/components/DatePicker/DatePickerMonths.tsx +101 -0
  147. package/src/components/DatePicker/DatePickerWeekdays.tsx +49 -0
  148. package/src/components/DatePicker/DatePickerYears.tsx +119 -0
  149. package/src/components/DatePicker/TimePicker.tsx +141 -0
  150. package/src/components/DatePicker/enums.ts +14 -0
  151. package/src/components/DatePicker/index.ts +13 -0
  152. package/src/components/DatePicker/polyfill.ts +21 -0
  153. package/src/components/DatePicker/time-picker/animated-math.ts +33 -0
  154. package/src/components/DatePicker/time-picker/period-native.tsx +34 -0
  155. package/src/components/DatePicker/time-picker/period-picker.tsx +16 -0
  156. package/src/components/DatePicker/time-picker/period-web.tsx +36 -0
  157. package/src/components/DatePicker/time-picker/wheel-native.tsx +37 -0
  158. package/src/components/DatePicker/time-picker/wheel-picker/index.ts +3 -0
  159. package/src/components/DatePicker/time-picker/wheel-picker/wheel-picker-item.tsx +132 -0
  160. package/src/components/DatePicker/time-picker/wheel-picker/wheel-picker.style.ts +22 -0
  161. package/src/components/DatePicker/time-picker/wheel-picker/wheel-picker.tsx +200 -0
  162. package/src/components/DatePicker/time-picker/wheel-web.tsx +181 -0
  163. package/src/components/DatePicker/time-picker/wheel.tsx +18 -0
  164. package/src/components/DatePicker/utils.ts +549 -0
  165. package/src/components/DatePickerInput/DatePickerInput.docs.mdx +237 -0
  166. package/src/components/DatePickerInput/DatePickerInput.props.ts +50 -0
  167. package/src/components/DatePickerInput/DatePickerInput.stories.tsx +178 -0
  168. package/src/components/DatePickerInput/DatePickerInput.tsx +265 -0
  169. package/src/components/DatePickerInput/DatePickerInputDoneButton.tsx +42 -0
  170. package/src/components/DatePickerInput/DatePickerInputDoneButton.web.tsx +7 -0
  171. package/src/components/DatePickerInput/index.ts +2 -0
  172. package/src/components/Icon/Icon.stories.tsx +4 -3
  173. package/src/components/Input/InputField.tsx +0 -2
  174. package/src/components/Input/InputSlot.tsx +14 -3
  175. package/src/components/Modal/Modal.stories.tsx +2 -30
  176. package/src/components/UnstyledIconButton/UnstyledIconButton.tsx +14 -2
  177. package/src/components/UnstyledIconButton/UnstyledIconButtonRoot.tsx +7 -3
  178. package/src/components/index.ts +2 -0
  179. package/src/hooks/index.ts +4 -3
  180. package/src/hooks/usePrevious.ts +9 -0
  181. package/src/hooks/useTheme.ts +4 -3
  182. package/src/tokens/components/dark/date-picker.ts +1 -0
  183. package/src/tokens/components/dark/illustrations.ts +7 -0
  184. package/src/tokens/components/dark/index.ts +1 -0
  185. package/src/tokens/components/dark/segmented-control.ts +2 -2
  186. package/src/tokens/components/dark/table.ts +3 -0
  187. package/src/tokens/components/light/date-picker.ts +1 -0
  188. package/src/tokens/components/light/illustrations.ts +7 -0
  189. package/src/tokens/components/light/index.ts +1 -0
  190. package/src/tokens/components/light/segmented-control.ts +2 -2
  191. package/src/tokens/components/light/table.ts +3 -0
  192. package/src/utils/index.ts +1 -0
  193. package/src/utils/isEqual.ts +30 -0
@@ -0,0 +1,21 @@
1
+ // @ts-ignore
2
+ Date.prototype._toLocaleString = Date.prototype.toLocaleString;
3
+ // @ts-ignore
4
+ Date.prototype.toLocaleString = function (a, b) {
5
+ if (b && Object.keys(b).length === 1 && 'timeZone' in b && a === 'en-US') {
6
+ return Intl.DateTimeFormat('en-us', {
7
+ year: 'numeric',
8
+ month: '2-digit',
9
+ day: '2-digit',
10
+ hour: '2-digit',
11
+ minute: '2-digit',
12
+ second: '2-digit',
13
+ hour12: false,
14
+ timeZone: b.timeZone,
15
+ })
16
+ .format(this)
17
+ .replace(/(\d{2})\/(\d{2})\/(\d{4}),/g, '$3-$1-$2');
18
+ }
19
+ // @ts-ignore
20
+ return this._toLocaleString(a, b);
21
+ };
@@ -0,0 +1,33 @@
1
+ import { Animated } from 'react-native';
2
+
3
+ const FACTORIAL_3 = 3 * 2;
4
+ const FACTORIAL_5 = 5 * 4 * FACTORIAL_3;
5
+ const FACTORIAL_7 = 7 * 6 * FACTORIAL_5;
6
+
7
+ function sin(animated: Animated.Animated) {
8
+ const normalized = normalize(animated);
9
+ const square = Animated.multiply(normalized, normalized);
10
+ const pow3 = Animated.multiply(normalized, square);
11
+ const pow5 = Animated.multiply(pow3, square);
12
+ const pow7 = Animated.multiply(pow5, square);
13
+
14
+ return Animated.add(
15
+ Animated.add(normalized, Animated.multiply(pow3, -1 / FACTORIAL_3)),
16
+ Animated.add(
17
+ Animated.multiply(pow5, 1 / FACTORIAL_5),
18
+ Animated.multiply(pow7, -1 / FACTORIAL_7)
19
+ )
20
+ );
21
+ }
22
+
23
+ function normalize(animated: Animated.Animated): Animated.Animated {
24
+ return Animated.add(
25
+ Animated.modulo(Animated.add(animated, Math.PI), Math.PI * 2),
26
+ -Math.PI
27
+ ).interpolate({
28
+ inputRange: [-Math.PI, -Math.PI / 2, Math.PI / 2, Math.PI],
29
+ outputRange: [0, -Math.PI / 2, Math.PI / 2, 0],
30
+ });
31
+ }
32
+
33
+ export { sin, normalize };
@@ -0,0 +1,34 @@
1
+ import { memo } from 'react';
2
+ import { PickerOption } from '../DatePicker.props';
3
+ import WheelPicker from './wheel-picker';
4
+
5
+ interface PeriodProps {
6
+ value: string;
7
+ setValue?: (value: any) => void;
8
+ }
9
+
10
+ const options: PickerOption[] = [
11
+ { value: 'AM', text: 'AM' },
12
+ { value: 'PM', text: 'PM' },
13
+ ];
14
+
15
+ const PeriodNative = ({ value, setValue = () => {} }: PeriodProps) => {
16
+ return (
17
+ <WheelPicker
18
+ value={value}
19
+ options={options}
20
+ onChange={setValue}
21
+ //containerStyle={defaultStyles.container}
22
+ itemHeight={44}
23
+ decelerationRate="fast"
24
+ />
25
+ );
26
+ };
27
+
28
+ const customComparator = (prev: Readonly<PeriodProps>, next: Readonly<PeriodProps>) => {
29
+ const areEqual = prev.value === next.value && prev.setValue === next.setValue;
30
+
31
+ return areEqual;
32
+ };
33
+
34
+ export default memo(PeriodNative, customComparator);
@@ -0,0 +1,16 @@
1
+ import { memo } from 'react';
2
+ import { Platform } from 'react-native';
3
+ import PeriodNative from './period-native';
4
+ import PeriodWeb from './period-web';
5
+
6
+ type PeriodProps = {
7
+ value: string;
8
+ setValue?: (value: any) => void;
9
+ };
10
+
11
+ const PeriodPicker = (props: PeriodProps) => {
12
+ const Component = Platform.OS === 'web' ? PeriodWeb : PeriodNative;
13
+ return <Component {...props} />;
14
+ };
15
+
16
+ export default memo(PeriodPicker);
@@ -0,0 +1,36 @@
1
+ import { memo } from 'react';
2
+ import { Pressable, View } from 'react-native';
3
+ import { StyleSheet } from 'react-native-unistyles';
4
+ import { BodyText } from '../../BodyText';
5
+
6
+ interface PeriodProps {
7
+ value: string;
8
+ setValue?: (value: any) => void;
9
+ }
10
+
11
+ const PeriodWeb = ({ value, setValue = () => {} }: PeriodProps) => {
12
+ return (
13
+ <Pressable onPress={() => setValue(value == 'AM' ? 'PM' : 'AM')}>
14
+ <View style={[styles.period]}>
15
+ <BodyText>{value}</BodyText>
16
+ </View>
17
+ </Pressable>
18
+ );
19
+ };
20
+
21
+ const styles = StyleSheet.create({
22
+ period: {
23
+ width: 65,
24
+ height: 44,
25
+ alignItems: 'center',
26
+ justifyContent: 'center',
27
+ },
28
+ });
29
+
30
+ const customComparator = (prev: Readonly<PeriodProps>, next: Readonly<PeriodProps>) => {
31
+ const areEqual = prev.value === next.value && prev.setValue === next.setValue;
32
+
33
+ return areEqual;
34
+ };
35
+
36
+ export default memo(PeriodWeb, customComparator);
@@ -0,0 +1,37 @@
1
+ import { memo } from 'react';
2
+ import { Platform } from 'react-native';
3
+ import { StyleSheet } from 'react-native-unistyles';
4
+ import { PickerOption } from '../DatePicker.props';
5
+ import WheelPicker from './wheel-picker';
6
+
7
+ interface WheelProps {
8
+ value: number | string;
9
+ setValue?: (value: any) => void;
10
+ items: PickerOption[];
11
+ }
12
+
13
+ const WheelNative = ({ value, setValue = () => {}, items }: WheelProps) => {
14
+ return (
15
+ <WheelPicker
16
+ value={value}
17
+ options={items}
18
+ onChange={setValue}
19
+ containerStyle={styles.container}
20
+ itemHeight={44}
21
+ decelerationRate="fast"
22
+ />
23
+ );
24
+ };
25
+
26
+ const styles = StyleSheet.create({
27
+ container: {
28
+ display: 'flex',
29
+ ...Platform.select({
30
+ web: {
31
+ userSelect: 'none',
32
+ },
33
+ }),
34
+ },
35
+ });
36
+
37
+ export default memo(WheelNative);
@@ -0,0 +1,3 @@
1
+ import WheelPicker from './wheel-picker';
2
+
3
+ export default WheelPicker;
@@ -0,0 +1,132 @@
1
+ import React from 'react';
2
+ import { Animated, StyleProp, ViewStyle } from 'react-native';
3
+ import { BodyText } from '../../../BodyText';
4
+ import { PickerOption } from '../../DatePicker.props';
5
+ import styles from './wheel-picker.style';
6
+
7
+ interface ItemProps {
8
+ style: StyleProp<ViewStyle>;
9
+ option: PickerOption | null;
10
+ height: number;
11
+ index: number;
12
+ currentScrollIndex: Animated.AnimatedAddition<number>;
13
+ visibleRest: number;
14
+ rotationFunction: (x: number) => number;
15
+ opacityFunction: (x: number) => number;
16
+ scaleFunction: (x: number) => number;
17
+ }
18
+
19
+ const WheelPickerItem: React.FC<ItemProps> = ({
20
+ style,
21
+ height,
22
+ option,
23
+ index,
24
+ visibleRest,
25
+ currentScrollIndex,
26
+ opacityFunction,
27
+ rotationFunction,
28
+ scaleFunction,
29
+ }) => {
30
+ const relativeScrollIndex = Animated.subtract(index, currentScrollIndex);
31
+
32
+ const translateY = relativeScrollIndex.interpolate({
33
+ inputRange: (() => {
34
+ const range = [0];
35
+ for (let i = 1; i <= visibleRest + 1; i++) {
36
+ range.unshift(-i);
37
+ range.push(i);
38
+ }
39
+ return range;
40
+ })(),
41
+ outputRange: (() => {
42
+ const range = [0];
43
+ for (let i = 1; i <= visibleRest + 1; i++) {
44
+ let y = (height / 2) * (1 - Math.sin(Math.PI / 2 - rotationFunction(i)));
45
+ for (let j = 1; j < i; j++) {
46
+ y += height * (1 - Math.sin(Math.PI / 2 - rotationFunction(j)));
47
+ }
48
+ range.unshift(y);
49
+ range.push(-y);
50
+ }
51
+ return range;
52
+ })(),
53
+ });
54
+
55
+ const opacity = relativeScrollIndex.interpolate({
56
+ inputRange: (() => {
57
+ const range = [0];
58
+ for (let i = 1; i <= visibleRest + 1; i++) {
59
+ range.unshift(-i);
60
+ range.push(i);
61
+ }
62
+ return range;
63
+ })(),
64
+ outputRange: (() => {
65
+ const range = [1];
66
+ for (let x = 1; x <= visibleRest + 1; x++) {
67
+ const y = opacityFunction(x);
68
+ range.unshift(y);
69
+ range.push(y);
70
+ }
71
+ return range;
72
+ })(),
73
+ });
74
+
75
+ const scale = relativeScrollIndex.interpolate({
76
+ inputRange: (() => {
77
+ const range = [0];
78
+ for (let i = 1; i <= visibleRest + 1; i++) {
79
+ range.unshift(-i);
80
+ range.push(i);
81
+ }
82
+ return range;
83
+ })(),
84
+ outputRange: (() => {
85
+ const range = [1.0];
86
+ for (let x = 1; x <= visibleRest + 1; x++) {
87
+ const y = scaleFunction(x);
88
+ range.unshift(y);
89
+ range.push(y);
90
+ }
91
+ return range;
92
+ })(),
93
+ });
94
+
95
+ const rotateX = relativeScrollIndex.interpolate({
96
+ inputRange: (() => {
97
+ const range = [0];
98
+ for (let i = 1; i <= visibleRest + 1; i++) {
99
+ range.unshift(-i);
100
+ range.push(i);
101
+ }
102
+ return range;
103
+ })(),
104
+ outputRange: (() => {
105
+ const range = ['0deg'];
106
+ for (let x = 1; x <= visibleRest + 1; x++) {
107
+ const y = rotationFunction(x);
108
+ range.unshift(`${y}deg`);
109
+ range.push(`${y}deg`);
110
+ }
111
+ return range;
112
+ })(),
113
+ });
114
+
115
+ return (
116
+ <Animated.View
117
+ style={[
118
+ styles.option,
119
+ style,
120
+ {
121
+ height,
122
+ opacity,
123
+ transform: [{ translateY }, { rotateX }, { scale }],
124
+ },
125
+ ]}
126
+ >
127
+ <BodyText>{option?.text}</BodyText>
128
+ </Animated.View>
129
+ );
130
+ };
131
+
132
+ export default React.memo(WheelPickerItem);
@@ -0,0 +1,22 @@
1
+ import { StyleSheet } from 'react-native-unistyles';
2
+
3
+ export default StyleSheet.create({
4
+ container: {
5
+ position: 'relative',
6
+ },
7
+ selectedIndicator: {
8
+ position: 'absolute',
9
+ width: '100%',
10
+ top: '50%',
11
+ },
12
+ scrollView: {
13
+ overflow: 'hidden',
14
+ flex: 1,
15
+ },
16
+ option: {
17
+ alignItems: 'center',
18
+ justifyContent: 'center',
19
+ paddingHorizontal: 16,
20
+ zIndex: 100,
21
+ },
22
+ });
@@ -0,0 +1,200 @@
1
+ import React, { memo, useEffect, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ Animated,
4
+ FlatList,
5
+ FlatListProps,
6
+ NativeScrollEvent,
7
+ NativeSyntheticEvent,
8
+ Platform,
9
+ StyleProp,
10
+ View,
11
+ ViewProps,
12
+ ViewStyle,
13
+ } from 'react-native';
14
+ import { PickerOption } from '../../DatePicker.props';
15
+ import WheelPickerItem from './wheel-picker-item';
16
+ import styles from './wheel-picker.style';
17
+
18
+ interface Props {
19
+ value: number | string;
20
+ options: PickerOption[];
21
+ onChange: (index: number | string) => void;
22
+ selectedIndicatorStyle?: StyleProp<ViewStyle>;
23
+ itemStyle?: ViewStyle;
24
+ itemHeight?: number;
25
+ containerStyle?: ViewStyle;
26
+ containerProps?: Omit<ViewProps, 'style'>;
27
+ scaleFunction?: (x: number) => number;
28
+ rotationFunction?: (x: number) => number;
29
+ opacityFunction?: (x: number) => number;
30
+ visibleRest?: number;
31
+ decelerationRate?: 'normal' | 'fast' | number;
32
+ flatListProps?: Omit<FlatListProps<PickerOption | null>, 'data' | 'renderItem'>;
33
+ }
34
+
35
+ const WheelPicker: React.FC<Props> = ({
36
+ value,
37
+ options,
38
+ onChange,
39
+ selectedIndicatorStyle = {},
40
+ containerStyle = {},
41
+ itemStyle = {},
42
+ itemHeight = 40,
43
+ scaleFunction = (x: number) => 1.0 ** x,
44
+ rotationFunction = (x: number) => 1 - Math.pow(1 / 2, x),
45
+ opacityFunction = (x: number) => Math.pow(1 / 3, x),
46
+ visibleRest = 2,
47
+ decelerationRate = 'normal',
48
+ containerProps = {},
49
+ flatListProps = {},
50
+ }) => {
51
+ const momentumStarted = useRef(false);
52
+ const selectedIndex = options.findIndex(item => item.value === value);
53
+
54
+ const flatListRef = useRef<FlatList<PickerOption | null>>(null);
55
+ const [scrollY] = useState(new Animated.Value(selectedIndex * itemHeight));
56
+
57
+ const containerHeight = (1 + visibleRest * 2) * itemHeight;
58
+ const paddedOptions = useMemo(() => {
59
+ const array: (PickerOption | null)[] = [...options];
60
+ for (let i = 0; i < visibleRest; i++) {
61
+ array.unshift(null);
62
+ array.push(null);
63
+ }
64
+ return array;
65
+ }, [options, visibleRest]);
66
+
67
+ const offsets = useMemo(
68
+ () => [...Array(paddedOptions.length)].map((_, i) => i * itemHeight),
69
+ [paddedOptions, itemHeight]
70
+ );
71
+
72
+ const currentScrollIndex = useMemo(
73
+ () => Animated.add(Animated.divide(scrollY, itemHeight), visibleRest),
74
+ [visibleRest, scrollY, itemHeight]
75
+ );
76
+
77
+ const handleScrollEnd = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
78
+ const offsetY = Math.min(
79
+ itemHeight * (options.length - 1),
80
+ Math.max(event.nativeEvent.contentOffset.y, 0)
81
+ );
82
+
83
+ let index = Math.floor(offsetY / itemHeight);
84
+ const remainder = offsetY % itemHeight;
85
+ if (remainder > itemHeight / 2) {
86
+ index++;
87
+ }
88
+
89
+ if (index !== selectedIndex) {
90
+ onChange(options[index]?.value || 0);
91
+ }
92
+ };
93
+
94
+ const handleMomentumScrollBegin = () => {
95
+ momentumStarted.current = true;
96
+ };
97
+
98
+ const handleMomentumScrollEnd = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
99
+ momentumStarted.current = false;
100
+ handleScrollEnd(event);
101
+ };
102
+
103
+ const handleScrollEndDrag = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
104
+ // Capture the offset value immediately
105
+ const offsetY = event.nativeEvent.contentOffset?.y;
106
+
107
+ // We'll start a short timer to see if momentum scroll begins
108
+ setTimeout(() => {
109
+ // If momentum scroll hasn't started within the timeout,
110
+ // then it was a slow scroll that won't trigger momentum
111
+ if (!momentumStarted.current && offsetY !== undefined) {
112
+ // Create a synthetic event with just the data we need
113
+ const syntheticEvent = {
114
+ nativeEvent: {
115
+ contentOffset: { y: offsetY },
116
+ },
117
+ };
118
+ handleScrollEnd(syntheticEvent as any);
119
+ }
120
+ }, 50);
121
+ };
122
+
123
+ useEffect(() => {
124
+ if (selectedIndex < 0 || selectedIndex >= options.length) {
125
+ throw new Error(
126
+ `Selected index ${selectedIndex} is out of bounds [0, ${options.length - 1}]`
127
+ );
128
+ }
129
+ }, [selectedIndex, options]);
130
+
131
+ /**
132
+ * If selectedIndex is changed from outside (not via onChange) we need to scroll to the specified index.
133
+ * This ensures that what the user sees as selected in the picker always corresponds to the value state.
134
+ */
135
+ useEffect(() => {
136
+ flatListRef.current?.scrollToIndex({
137
+ index: selectedIndex,
138
+ animated: Platform.OS === 'ios',
139
+ });
140
+ }, [selectedIndex, itemHeight]);
141
+
142
+ return (
143
+ <View
144
+ style={[styles.container, { height: containerHeight }, containerStyle]}
145
+ {...containerProps}
146
+ >
147
+ <View
148
+ style={[
149
+ styles.selectedIndicator,
150
+ selectedIndicatorStyle,
151
+ {
152
+ transform: [{ translateY: -itemHeight / 2 }],
153
+ height: itemHeight,
154
+ },
155
+ ]}
156
+ />
157
+ <Animated.FlatList<PickerOption | null>
158
+ {...flatListProps}
159
+ ref={flatListRef}
160
+ nestedScrollEnabled
161
+ style={styles.scrollView}
162
+ showsVerticalScrollIndicator={false}
163
+ onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: scrollY } } }], {
164
+ useNativeDriver: true,
165
+ })}
166
+ onScrollEndDrag={handleScrollEndDrag}
167
+ onMomentumScrollBegin={handleMomentumScrollBegin}
168
+ onMomentumScrollEnd={handleMomentumScrollEnd}
169
+ snapToOffsets={offsets}
170
+ decelerationRate={decelerationRate}
171
+ initialScrollIndex={selectedIndex}
172
+ getItemLayout={(_, index) => ({
173
+ length: itemHeight,
174
+ offset: itemHeight * index,
175
+ index,
176
+ })}
177
+ data={paddedOptions}
178
+ keyExtractor={(item, index) =>
179
+ item ? `${item.value}-${item.text}-${index}` : `null-${index}`
180
+ }
181
+ renderItem={({ item: option, index }) => (
182
+ <WheelPickerItem
183
+ key={`option-${index}`}
184
+ index={index}
185
+ option={option}
186
+ style={itemStyle}
187
+ height={itemHeight}
188
+ currentScrollIndex={currentScrollIndex}
189
+ scaleFunction={scaleFunction}
190
+ rotationFunction={rotationFunction}
191
+ opacityFunction={opacityFunction}
192
+ visibleRest={visibleRest}
193
+ />
194
+ )}
195
+ />
196
+ </View>
197
+ );
198
+ };
199
+
200
+ export default memo(WheelPicker);