react-native-molecules 0.5.0-beta.3 → 0.5.0-beta.30

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 (227) hide show
  1. package/components/Accordion/Accordion.tsx +2 -6
  2. package/components/Accordion/AccordionItem.tsx +16 -12
  3. package/components/Accordion/AccordionItemContent.tsx +6 -1
  4. package/components/Accordion/AccordionItemHeader.tsx +1 -1
  5. package/components/Accordion/utils.ts +6 -0
  6. package/components/ActivityIndicator/ActivityIndicator.tsx +6 -15
  7. package/components/Appbar/AppbarBase.tsx +18 -13
  8. package/components/Button/Button.tsx +211 -264
  9. package/components/Button/index.tsx +9 -3
  10. package/components/Button/types.ts +16 -2
  11. package/components/Button/utils.ts +230 -208
  12. package/components/Card/Card.tsx +1 -1
  13. package/components/Checkbox/Checkbox.tsx +125 -88
  14. package/components/Checkbox/CheckboxBase.ios.tsx +14 -23
  15. package/components/Checkbox/CheckboxBase.tsx +21 -137
  16. package/components/Checkbox/context.tsx +14 -0
  17. package/components/Checkbox/index.tsx +11 -4
  18. package/components/Checkbox/types.ts +63 -29
  19. package/components/Checkbox/utils.ts +25 -108
  20. package/components/Chip/Chip.tsx +41 -52
  21. package/components/Chip/utils.ts +3 -7
  22. package/components/DateField/DateField.tsx +111 -0
  23. package/components/DateField/index.tsx +6 -0
  24. package/components/{DatePickerInput/inputUtils.ts → DateField/useDateFieldState.ts} +19 -51
  25. package/components/DatePicker/DateCalendar.tsx +83 -0
  26. package/components/DatePicker/DatePickerActions.tsx +73 -0
  27. package/components/DatePicker/DatePickerModal.tsx +246 -0
  28. package/components/DatePicker/DatePickerPopover.tsx +79 -0
  29. package/components/DatePicker/DatePickerProvider.tsx +158 -0
  30. package/components/DatePicker/DatePickerTrigger.tsx +23 -0
  31. package/components/DatePicker/context.tsx +83 -0
  32. package/components/DatePicker/index.tsx +45 -0
  33. package/components/DatePicker/utils.ts +295 -0
  34. package/components/DatePickerInline/DatePickerDockedHeader.tsx +117 -0
  35. package/components/DatePickerInline/DatePickerInline.tsx +17 -16
  36. package/components/DatePickerInline/DatePickerInlineBase.tsx +11 -5
  37. package/components/DatePickerInline/DatePickerInlineHeader.tsx +50 -20
  38. package/components/DatePickerInline/Day.tsx +25 -1
  39. package/components/DatePickerInline/DayNames.tsx +13 -10
  40. package/components/DatePickerInline/DayRange.tsx +2 -4
  41. package/components/DatePickerInline/HeaderItem.tsx +44 -29
  42. package/components/DatePickerInline/Month.tsx +48 -67
  43. package/components/DatePickerInline/MonthPicker.tsx +80 -92
  44. package/components/DatePickerInline/Swiper.native.tsx +21 -4
  45. package/components/DatePickerInline/Swiper.tsx +169 -14
  46. package/components/DatePickerInline/SwiperUtils.ts +1 -1
  47. package/components/DatePickerInline/Week.tsx +6 -1
  48. package/components/DatePickerInline/YearPicker.tsx +220 -78
  49. package/components/DatePickerInline/dateUtils.tsx +18 -13
  50. package/components/DatePickerInline/store.tsx +27 -0
  51. package/components/DatePickerInline/types.ts +6 -2
  52. package/components/DatePickerInline/utils.ts +66 -29
  53. package/components/Divider/Divider.tsx +192 -0
  54. package/components/Divider/index.tsx +10 -0
  55. package/components/Drawer/Drawer.tsx +17 -6
  56. package/components/Drawer/DrawerItemGroup.tsx +3 -7
  57. package/components/ElementGroup/ElementGroup.tsx +1 -1
  58. package/components/FilePicker/FilePicker.tsx +48 -78
  59. package/components/FilePicker/index.tsx +2 -1
  60. package/components/FilePicker/utils.ts +9 -0
  61. package/components/HelperText/HelperText.tsx +0 -35
  62. package/components/Icon/iconFactory.tsx +5 -4
  63. package/components/Icon/index.tsx +1 -1
  64. package/components/Icon/types.ts +17 -6
  65. package/components/IconButton/IconButton.tsx +84 -84
  66. package/components/IconButton/index.tsx +1 -0
  67. package/components/IconButton/types.ts +10 -0
  68. package/components/IconButton/utils.ts +167 -33
  69. package/components/List/List.tsx +276 -0
  70. package/components/List/context.tsx +27 -0
  71. package/components/List/index.ts +8 -0
  72. package/components/List/types.ts +117 -0
  73. package/components/List/utils.ts +79 -0
  74. package/components/LoadingIndicator/LoadingIndicator.tsx +253 -0
  75. package/components/LoadingIndicator/LoadingIndicator.web.tsx +136 -0
  76. package/components/LoadingIndicator/index.tsx +13 -0
  77. package/components/LoadingIndicator/utils.ts +117 -0
  78. package/components/Menu/Menu.tsx +162 -39
  79. package/components/Menu/index.tsx +10 -7
  80. package/components/Menu/utils.ts +21 -70
  81. package/components/NavigationRail/NavigationRail.tsx +15 -9
  82. package/components/Popover/Popover.tsx +119 -145
  83. package/components/Popover/PopoverRoot.tsx +60 -0
  84. package/components/Popover/common.ts +54 -34
  85. package/components/Popover/index.ts +12 -1
  86. package/components/Popover/usePlatformMeasure.native.ts +90 -0
  87. package/components/Popover/usePlatformMeasure.ts +120 -0
  88. package/components/Popover/utils.ts +34 -0
  89. package/components/Portal/Portal.tsx +1 -2
  90. package/components/Radio/Radio.tsx +188 -0
  91. package/components/Radio/RadioBase.ios.tsx +69 -0
  92. package/components/Radio/RadioBase.tsx +136 -0
  93. package/components/Radio/context.tsx +23 -0
  94. package/components/Radio/index.tsx +20 -0
  95. package/components/Radio/types.ts +101 -0
  96. package/components/Radio/utils.ts +115 -0
  97. package/components/Rating/Rating.tsx +1 -1
  98. package/components/Select/Select.tsx +521 -785
  99. package/components/Select/context.tsx +81 -0
  100. package/components/Select/index.ts +26 -14
  101. package/components/Select/types.ts +65 -58
  102. package/components/Select/utils.ts +126 -0
  103. package/components/Slot/Slot.tsx +244 -0
  104. package/components/Slot/compose-refs.tsx +62 -0
  105. package/components/Slot/index.tsx +8 -0
  106. package/components/Surface/Surface.android.tsx +32 -7
  107. package/components/Surface/Surface.ios.tsx +34 -29
  108. package/components/Surface/Surface.tsx +31 -4
  109. package/components/Surface/utils.ts +44 -6
  110. package/components/Switch/Switch.ios.tsx +1 -1
  111. package/components/Switch/Switch.tsx +10 -3
  112. package/components/Tabs/TabItem.tsx +35 -58
  113. package/components/Tabs/TabLabel.tsx +5 -9
  114. package/components/Tabs/Tabs.tsx +156 -150
  115. package/components/Tabs/utils.ts +15 -2
  116. package/components/Text/textFactory.tsx +17 -5
  117. package/components/TextInput/TextInput.tsx +663 -579
  118. package/components/TextInput/index.tsx +19 -3
  119. package/components/TextInput/types.ts +77 -28
  120. package/components/TextInput/utils.ts +235 -145
  121. package/components/TimeField/TimeField.tsx +75 -0
  122. package/components/TimeField/index.tsx +6 -0
  123. package/components/TimeField/useTimeFieldState.ts +70 -0
  124. package/components/{TimePickerField/sanitizeTime.ts → TimeField/utils.ts} +77 -10
  125. package/components/TimePicker/AnalogClock.tsx +1 -1
  126. package/components/TimePicker/TimeInput.tsx +87 -42
  127. package/components/TimePicker/TimeInputs.tsx +138 -50
  128. package/components/TimePicker/TimePicker.tsx +74 -11
  129. package/components/TimePicker/TimePickerModal.tsx +186 -0
  130. package/components/TimePicker/context.tsx +17 -0
  131. package/components/TimePicker/index.tsx +15 -3
  132. package/components/TimePicker/utils.ts +93 -4
  133. package/components/Tooltip/Tooltip.tsx +42 -67
  134. package/components/Tooltip/TooltipContent.tsx +32 -5
  135. package/components/Tooltip/TooltipTrigger.tsx +20 -20
  136. package/components/Tooltip/index.tsx +1 -1
  137. package/components/TouchableRipple/TouchableRipple.native.tsx +83 -16
  138. package/components/TouchableRipple/TouchableRipple.tsx +150 -102
  139. package/components/TouchableRipple/rippleFromForegroundColor.ts +21 -0
  140. package/hocs/index.tsx +1 -1
  141. package/hocs/withKeyboardAccessibility.tsx +2 -3
  142. package/hocs/withPortal.tsx +1 -1
  143. package/hooks/index.tsx +2 -12
  144. package/hooks/useActionState.tsx +19 -8
  145. package/hooks/useContrastColor.ts +1 -2
  146. package/hooks/useFilePicker.tsx +7 -17
  147. package/hooks/useHandleNumberFormat.tsx +2 -2
  148. package/hooks/useMediaQuery.tsx +1 -2
  149. package/package.json +95 -111
  150. package/shortcuts-manager/ShortcutsManager/ShortcutsManager.tsx +6 -3
  151. package/shortcuts-manager/ShortcutsManager/utils.tsx +1 -1
  152. package/shortcuts-manager/useSetScopes/useSetScopes.tsx +1 -1
  153. package/shortcuts-manager/useShortcut/useShortcut.tsx +1 -1
  154. package/styles/shadow.ts +2 -1
  155. package/styles/themes/LightTheme.tsx +1 -1
  156. package/utils/DocumentPicker/documentPicker.ts +78 -27
  157. package/utils/DocumentPicker/types.ts +0 -1
  158. package/utils/extractSubcomponents.ts +89 -0
  159. package/utils/extractTextStyles.ts +1 -2
  160. package/utils/formatNumberWithMask/formatNumberWithMask.ts +2 -1
  161. package/utils/index.ts +0 -3
  162. package/utils/normalizeToNumberString/normalizeToNumberString.ts +1 -1
  163. package/components/DatePickerDocked/DatePickerDocked.tsx +0 -30
  164. package/components/DatePickerDocked/DatePickerDockedHeader.tsx +0 -129
  165. package/components/DatePickerDocked/index.tsx +0 -17
  166. package/components/DatePickerDocked/types.ts +0 -11
  167. package/components/DatePickerDocked/utils.ts +0 -157
  168. package/components/DatePickerInline/DatePickerContext.tsx +0 -21
  169. package/components/DatePickerInput/DatePickerInput.tsx +0 -139
  170. package/components/DatePickerInput/DatePickerInputModal.tsx +0 -48
  171. package/components/DatePickerInput/DatePickerInputWithoutModal.tsx +0 -77
  172. package/components/DatePickerInput/DateRangeInput.tsx +0 -88
  173. package/components/DatePickerInput/index.tsx +0 -10
  174. package/components/DatePickerInput/types.ts +0 -28
  175. package/components/DatePickerInput/utils.ts +0 -15
  176. package/components/DatePickerModal/AnimatedCrossView.tsx +0 -94
  177. package/components/DatePickerModal/CalendarEdit.tsx +0 -139
  178. package/components/DatePickerModal/DatePickerModal.tsx +0 -85
  179. package/components/DatePickerModal/DatePickerModalContent.tsx +0 -155
  180. package/components/DatePickerModal/DatePickerModalContentHeader.tsx +0 -213
  181. package/components/DatePickerModal/DatePickerModalHeader.tsx +0 -74
  182. package/components/DatePickerModal/DatePickerModalHeaderBackground.tsx +0 -13
  183. package/components/DatePickerModal/index.tsx +0 -16
  184. package/components/DatePickerModal/types.ts +0 -92
  185. package/components/DatePickerModal/utils.ts +0 -122
  186. package/components/DateTimePicker/DateTimePicker.tsx +0 -172
  187. package/components/DateTimePicker/index.tsx +0 -10
  188. package/components/DateTimePicker/utils.ts +0 -12
  189. package/components/HorizontalDivider/HorizontalDivider.tsx +0 -103
  190. package/components/HorizontalDivider/index.tsx +0 -9
  191. package/components/ListItem/ListItem.tsx +0 -136
  192. package/components/ListItem/ListItemDescription.tsx +0 -25
  193. package/components/ListItem/ListItemTitle.tsx +0 -25
  194. package/components/ListItem/index.tsx +0 -14
  195. package/components/ListItem/utils.ts +0 -115
  196. package/components/Menu/MenuDivider.tsx +0 -13
  197. package/components/Menu/MenuItem.tsx +0 -128
  198. package/components/Popover/Popover.native.tsx +0 -185
  199. package/components/RadioButton/RadioButton.tsx +0 -138
  200. package/components/RadioButton/RadioButtonAndroid.tsx +0 -188
  201. package/components/RadioButton/RadioButtonGroup.tsx +0 -98
  202. package/components/RadioButton/RadioButtonIOS.tsx +0 -106
  203. package/components/RadioButton/RadioButtonItem.tsx +0 -232
  204. package/components/RadioButton/index.ts +0 -22
  205. package/components/RadioButton/utils.ts +0 -165
  206. package/components/TimePickerField/TimePickerField.tsx +0 -152
  207. package/components/TimePickerField/index.tsx +0 -10
  208. package/components/TimePickerField/utils.ts +0 -94
  209. package/components/TimePickerModal/TimePickerModal.tsx +0 -115
  210. package/components/TimePickerModal/index.tsx +0 -10
  211. package/components/TimePickerModal/utils.ts +0 -47
  212. package/components/VerticalDivider/VerticalDivider.tsx +0 -100
  213. package/components/VerticalDivider/index.tsx +0 -9
  214. package/context-bridge/index.tsx +0 -87
  215. package/fast-context/index.tsx +0 -190
  216. package/hocs/typedMemo.tsx +0 -5
  217. package/hooks/useControlledValue.tsx +0 -68
  218. package/hooks/useLatest.tsx +0 -9
  219. package/hooks/useMergedRefs.ts +0 -14
  220. package/hooks/usePrevious.ts +0 -13
  221. package/hooks/useSearchable.tsx +0 -74
  222. package/hooks/useSubcomponents.tsx +0 -59
  223. package/hooks/useToggle.tsx +0 -24
  224. package/utils/color.ts +0 -22
  225. package/utils/compare/index.ts +0 -54
  226. package/utils/lodash.ts +0 -49
  227. package/utils/repository.ts +0 -53
@@ -1,3 +1,4 @@
1
+ import { useLatest } from '@react-native-molecules/utils/hooks';
1
2
  import {
2
3
  type CSSProperties,
3
4
  memo,
@@ -9,12 +10,12 @@ import {
9
10
  useRef,
10
11
  useState,
11
12
  } from 'react';
12
- import { StyleSheet, View } from 'react-native';
13
13
 
14
- import { useLatest } from '../../hooks';
15
14
  import AutoSizer from './AutoSizer';
16
- import { beginOffset, estimatedMonthHeight, totalMonths } from './dateUtils';
15
+ import { beginOffset, estimatedMonthHeight, getInitialIndex, totalMonths } from './dateUtils';
16
+ import { addMonths, getRealIndex } from './dateUtils';
17
17
  import { getIndexFromVerticalOffset, getMonthHeight, getVerticalMonthsOffset } from './Month';
18
+ import { useDatePickerInlineStore } from './store';
18
19
  import type { SwiperProps } from './SwiperUtils';
19
20
  import { montHeaderHeight } from './utils';
20
21
 
@@ -25,7 +26,16 @@ function Swiper({ scrollMode, renderItem, renderHeader, renderFooter, initialInd
25
26
  <>
26
27
  {renderHeader && renderHeader()}
27
28
  {isHorizontal ? (
28
- <View style={styles.flex1}>{renderItem(initialIndex)}</View>
29
+ <AutoSizer>
30
+ {({ width, height }) => (
31
+ <HorizontalScroller
32
+ width={width}
33
+ height={height}
34
+ initialIndex={initialIndex}
35
+ renderItem={renderItem}
36
+ />
37
+ )}
38
+ </AutoSizer>
29
39
  ) : (
30
40
  <AutoSizer>
31
41
  {({ width, height }) => (
@@ -45,6 +55,142 @@ function Swiper({ scrollMode, renderItem, renderHeader, renderFooter, initialInd
45
55
  }
46
56
 
47
57
  const visibleArray = (i: number) => [i - 2, i - 1, i, i + 1, i + 2];
58
+ const visibleHorizontalArray = (i: number) => [i - 1, i, i + 1];
59
+
60
+ function HorizontalScroller({
61
+ width,
62
+ height,
63
+ initialIndex,
64
+ renderItem,
65
+ }: {
66
+ renderItem: (index: number) => any;
67
+ width: number;
68
+ height: number;
69
+ initialIndex: number;
70
+ }) {
71
+ const idx = useRef<number>(initialIndex);
72
+ const [visibleIndexes, setVisibleIndexes] = useState<number[]>(
73
+ visibleHorizontalArray(initialIndex),
74
+ );
75
+ const parentRef = useRef<HTMLDivElement | null>(null);
76
+ const [{ localDate }, setStore] = useDatePickerInlineStore(state => state);
77
+ const settleTimerRef = useRef<number | null>(null);
78
+ const isRecenteringRef = useRef(false);
79
+
80
+ const syncIndex = useCallback(
81
+ (index: number) => {
82
+ idx.current = index;
83
+ setVisibleIndexes(visibleHorizontalArray(index));
84
+ setStore(prev => ({
85
+ ...prev,
86
+ localDate: addMonths(new Date(), getRealIndex(index)),
87
+ }));
88
+ },
89
+ [setStore],
90
+ );
91
+
92
+ const scrollToCenter = useCallback(
93
+ (behavior: ScrollBehavior = 'auto') => {
94
+ const element = parentRef.current;
95
+ if (!element) return;
96
+
97
+ isRecenteringRef.current = true;
98
+ element.scrollTo({
99
+ left: width,
100
+ top: 0,
101
+ behavior,
102
+ });
103
+ window.requestAnimationFrame(() => {
104
+ isRecenteringRef.current = false;
105
+ });
106
+ },
107
+ [width],
108
+ );
109
+
110
+ useIsomorphicLayoutEffect(() => {
111
+ scrollToCenter();
112
+ }, [scrollToCenter, visibleIndexes]);
113
+
114
+ useEffect(() => {
115
+ const targetIndex = getInitialIndex(localDate);
116
+ if (targetIndex === idx.current) return;
117
+ idx.current = targetIndex;
118
+ setVisibleIndexes(visibleHorizontalArray(targetIndex));
119
+ }, [localDate]);
120
+
121
+ useEffect(() => {
122
+ return () => {
123
+ if (settleTimerRef.current) {
124
+ window.clearTimeout(settleTimerRef.current);
125
+ }
126
+ };
127
+ }, []);
128
+
129
+ const onScroll = useCallback(
130
+ (e: UIEvent) => {
131
+ if (isRecenteringRef.current) {
132
+ return;
133
+ }
134
+
135
+ const left = e.currentTarget.scrollLeft;
136
+
137
+ if (settleTimerRef.current) {
138
+ window.clearTimeout(settleTimerRef.current);
139
+ }
140
+
141
+ settleTimerRef.current = window.setTimeout(() => {
142
+ const direction = left > width * 1.5 ? 1 : left < width * 0.5 ? -1 : 0;
143
+
144
+ if (direction !== 0) {
145
+ syncIndex(idx.current + direction);
146
+ }
147
+
148
+ scrollToCenter(direction === 0 ? 'smooth' : 'auto');
149
+ }, 120);
150
+ },
151
+ [scrollToCenter, syncIndex, width],
152
+ );
153
+
154
+ const { containerStyle, innerContainerStyle, itemContainerStyle } = useMemo(() => {
155
+ return {
156
+ containerStyle: {
157
+ height,
158
+ width,
159
+ overflowX: 'auto',
160
+ overflowY: 'hidden',
161
+ scrollSnapType: 'x mandatory',
162
+ WebkitOverflowScrolling: 'touch',
163
+ scrollbarWidth: 'none',
164
+ msOverflowStyle: 'none',
165
+ },
166
+ innerContainerStyle: {
167
+ width: width * 3,
168
+ height,
169
+ position: 'relative',
170
+ },
171
+ itemContainerStyle: (vi: number) => ({
172
+ left: width * vi,
173
+ top: 0,
174
+ bottom: 0,
175
+ position: 'absolute',
176
+ width,
177
+ scrollSnapAlign: 'start',
178
+ }),
179
+ };
180
+ }, [height, width]);
181
+
182
+ return (
183
+ <div ref={parentRef} style={containerStyle as CSSProperties} onScroll={onScroll}>
184
+ <div style={innerContainerStyle as CSSProperties}>
185
+ {[0, 1, 2].map(vi => (
186
+ <div key={vi} style={itemContainerStyle(vi) as CSSProperties}>
187
+ {renderItem(visibleIndexes[vi])}
188
+ </div>
189
+ ))}
190
+ </div>
191
+ </div>
192
+ );
193
+ }
48
194
 
49
195
  function VerticalScroller({
50
196
  width,
@@ -63,19 +209,30 @@ function VerticalScroller({
63
209
  const [visibleIndexes, setVisibleIndexes] = useState<number[]>(visibleArray(initialIndex));
64
210
 
65
211
  const parentRef = useRef<HTMLDivElement | null>(null);
212
+ const [{ localDate }, setStore] = useDatePickerInlineStore(state => state);
66
213
 
67
214
  useIsomorphicLayoutEffect(() => {
68
215
  const element = parentRef.current;
69
216
  if (!element) {
70
217
  return;
71
218
  }
72
- const top = getVerticalMonthsOffset(idx.current) - montHeaderHeight;
73
-
74
219
  element.scrollTo({
75
- top,
220
+ top: getVerticalMonthsOffset(idx.current) - montHeaderHeight,
76
221
  });
77
222
  }, [parentRef, idx]);
78
223
 
224
+ useEffect(() => {
225
+ const targetIndex = getInitialIndex(localDate);
226
+ if (targetIndex === idx.current) return;
227
+ idx.current = targetIndex;
228
+ setVisibleIndexes(visibleArray(targetIndex));
229
+ const element = parentRef.current;
230
+ if (!element) return;
231
+ element.scrollTo({
232
+ top: getVerticalMonthsOffset(targetIndex) - montHeaderHeight,
233
+ });
234
+ }, [localDate]);
235
+
79
236
  const setVisibleIndexesThrottled = useDebouncedCallback(setVisibleIndexes);
80
237
 
81
238
  const onScroll = useCallback(
@@ -91,9 +248,13 @@ function VerticalScroller({
91
248
  if (idx.current !== index) {
92
249
  idx.current = index;
93
250
  setVisibleIndexesThrottled(visibleArray(index));
251
+ setStore(prev => ({
252
+ ...prev,
253
+ localDate: addMonths(new Date(), getRealIndex(index)),
254
+ }));
94
255
  }
95
256
  },
96
- [setVisibleIndexesThrottled],
257
+ [setStore, setVisibleIndexesThrottled],
97
258
  );
98
259
 
99
260
  const { containerStyle, innerContainerStyle, itemContainerStyle } = useMemo(() => {
@@ -132,12 +293,6 @@ function VerticalScroller({
132
293
  );
133
294
  }
134
295
 
135
- const styles = StyleSheet.create({
136
- flex1: {
137
- flex: 1,
138
- },
139
- });
140
-
141
296
  export function useDebouncedCallback(callback: any): any {
142
297
  const mounted = useRef<boolean>(true);
143
298
  const latest = useLatest(callback);
@@ -1,6 +1,6 @@
1
+ import { useLatest } from '@react-native-molecules/utils/hooks';
1
2
  import { type RefObject, useEffect } from 'react';
2
3
 
3
- import { useLatest } from '../../hooks';
4
4
  import { addMonths, differenceInMonths, getRealIndex, startAtIndex } from './dateUtils';
5
5
 
6
6
  export type SwiperProps = {
@@ -10,6 +10,7 @@ type Props = ViewProps & {
10
10
  generatedDays: {
11
11
  beforeWeekDay: boolean;
12
12
  afterWeekDay: boolean;
13
+ outside: boolean;
13
14
  year: number;
14
15
  month: number;
15
16
  dayOfMonth: number;
@@ -24,6 +25,7 @@ type Props = ViewProps & {
24
25
  }[];
25
26
  onPressDate: (date: Date) => any;
26
27
  disableWeekDays?: DisableWeekDaysType;
28
+ showOutsideDays?: boolean;
27
29
  };
28
30
 
29
31
  const Week = ({
@@ -31,6 +33,7 @@ const Week = ({
31
33
  generatedDays,
32
34
  onPressDate,
33
35
  disableWeekDays,
36
+ showOutsideDays,
34
37
  style,
35
38
  ...rest
36
39
  }: Props) => {
@@ -39,9 +42,10 @@ const Week = ({
39
42
  {generatedDays
40
43
  .filter(gd => showWeekDay(gd.dayIndex, disableWeekDays))
41
44
  .map(gd => {
45
+ const isOutside = gd.beforeWeekDay || gd.afterWeekDay;
42
46
  return (
43
47
  <Fragment key={gd.dayIndex + weekIndex}>
44
- {gd.beforeWeekDay || gd.afterWeekDay ? (
48
+ {isOutside && !showOutsideDays ? (
45
49
  <EmptyDay />
46
50
  ) : (
47
51
  <Day
@@ -55,6 +59,7 @@ const Week = ({
55
59
  onPressDate={onPressDate}
56
60
  isToday={gd.isToday}
57
61
  disabled={gd.disabled}
62
+ outside={isOutside}
58
63
  />
59
64
  )}
60
65
  </Fragment>
@@ -1,139 +1,281 @@
1
1
  import { setYear } from 'date-fns';
2
- import { memo, useCallback, useEffect, useMemo, useRef } from 'react';
2
+ import { memo, useCallback, useLayoutEffect, useMemo, useRef } from 'react';
3
3
  import { FlatList, StyleSheet, View } from 'react-native';
4
4
 
5
5
  import { getYearRange, resolveStateVariant } from '../../utils';
6
- import { HorizontalDivider } from '../HorizontalDivider';
7
- import { ListItem } from '../ListItem';
8
- import { useDatePickerStore } from './DatePickerContext';
6
+ import { datePickerMonthItemStyles, datePickerMonthPickerStyles } from '../DatePicker/utils';
7
+ import { Divider } from '../Divider';
8
+ import { Icon } from '../Icon';
9
+ import { List, type ListItemId, useListContextValue } from '../List';
10
+ import { Text } from '../Text';
11
+ import { useDatePickerInlineStore } from './store';
9
12
  import { datePickerYearItemStyles, datePickerYearPickerStyles } from './utils';
10
13
 
11
- const ITEM_HEIGHT = 62;
14
+ type YearListItem = { id: number; label: string };
12
15
 
13
- export default function YearPicker() {
14
- const [{ startDateYear, endDateYear, localDate, pickerType }, setStore] = useDatePickerStore(
15
- state => state,
16
- );
17
- const flatList = useRef<FlatList<number> | null>(null);
16
+ const GRID_ITEM_HEIGHT = 62;
17
+ const NUM_COLUMNS = 3;
18
+ const LIST_ITEM_HEIGHT = 46;
19
+
20
+ type YearPickerProps = {
21
+ layout?: 'grid' | 'list';
22
+ };
23
+
24
+ export default function YearPicker({ layout = 'grid' }: YearPickerProps) {
25
+ if (layout === 'list') return <YearPickerList />;
26
+ return <YearPickerGrid />;
27
+ }
28
+
29
+ function YearPickerGrid() {
30
+ const [{ startDateYear, endDateYear, localDate, pickerType }, setStore] =
31
+ useDatePickerInlineStore(state => state);
18
32
  const years = useMemo(
19
33
  () => getYearRange(startDateYear, endDateYear),
20
34
  [startDateYear, endDateYear],
21
35
  );
36
+ const yearItems = useMemo(
37
+ () => years.map(year => ({ id: year, label: String(year) })),
38
+ [years],
39
+ );
22
40
  const selectingYear = pickerType === 'year';
23
41
  const selectedYear = localDate.getFullYear();
42
+ const flatListRef = useRef<FlatList<YearListItem> | null>(null);
43
+
44
+ const initialScrollOffset = useMemo(() => {
45
+ if (years.length === 0) return 0;
46
+ const totalRows = Math.ceil(years.length / NUM_COLUMNS);
47
+ const unclampedRow = Math.floor((selectedYear - years[0]) / NUM_COLUMNS);
48
+ const rowIndex = Math.min(Math.max(0, unclampedRow), Math.max(0, totalRows - 1));
49
+ return rowIndex * GRID_ITEM_HEIGHT;
50
+ }, [selectedYear, years]);
24
51
 
25
- // scroll to selected year
26
- useEffect(() => {
27
- if (flatList.current && selectingYear && selectedYear) {
28
- const indexToGo = selectedYear - years[0];
29
- flatList.current.scrollToOffset({
30
- offset: (indexToGo / 3) * ITEM_HEIGHT - ITEM_HEIGHT,
31
- animated: false,
32
- });
33
- }
34
- }, [flatList, selectedYear, selectingYear, years]);
52
+ useLayoutEffect(() => {
53
+ if (!selectingYear) return;
54
+ flatListRef.current?.scrollToOffset({ offset: initialScrollOffset, animated: false });
55
+ }, [selectingYear, initialScrollOffset]);
35
56
 
36
57
  const { containerStyle, yearStyle } = useMemo(() => {
37
58
  const { backgroundColor, ...rest } = datePickerYearPickerStyles.root;
38
-
39
59
  return {
40
60
  containerStyle: [
41
61
  StyleSheet.absoluteFill,
42
- styles.root,
62
+ gridStyles.root,
43
63
  { backgroundColor },
44
64
  datePickerYearPickerStyles.yearContainer,
45
- selectingYear ? styles.opacity1 : styles.opacity0,
65
+ selectingYear ? gridStyles.opacity1 : gridStyles.opacity0,
46
66
  ],
47
67
  yearStyle: rest,
48
68
  };
49
69
  }, [selectingYear]);
50
70
 
51
71
  const handleOnChange = useCallback(
52
- (year: number) => {
72
+ (value: ListItemId | null) => {
73
+ if (typeof value !== 'number') return;
53
74
  setStore(prev => ({
54
- localDate: setYear(prev.localDate, year),
75
+ localDate: setYear(prev.localDate, value),
55
76
  pickerType: undefined,
56
77
  }));
57
78
  },
58
79
  [setStore],
59
80
  );
60
81
 
82
+ const getRowLayout = useCallback(
83
+ (_data: ArrayLike<YearListItem> | null | undefined, index: number) => ({
84
+ length: GRID_ITEM_HEIGHT,
85
+ offset: GRID_ITEM_HEIGHT * index,
86
+ index,
87
+ }),
88
+ [],
89
+ );
90
+
61
91
  return (
62
- <>
63
- {selectingYear && (
64
- <View style={containerStyle} pointerEvents={selectingYear ? 'auto' : 'none'}>
65
- <HorizontalDivider />
66
- <FlatList<number>
67
- ref={flatList}
68
- style={styles.list}
69
- data={years}
70
- renderItem={({ item }) => (
71
- <Year
72
- year={item}
73
- selected={selectedYear === item}
74
- onPressYear={handleOnChange}
75
- yearStyles={yearStyle}
76
- />
77
- )}
78
- keyExtractor={item => `${item}`}
79
- numColumns={3}
80
- />
81
- </View>
82
- )}
83
- </>
92
+ <List multiple={false} value={selectedYear} onChange={handleOnChange}>
93
+ <View style={containerStyle} pointerEvents={selectingYear ? 'auto' : 'none'}>
94
+ <Divider />
95
+ <FlatList<YearListItem>
96
+ ref={flatListRef}
97
+ style={gridStyles.list}
98
+ data={yearItems}
99
+ numColumns={NUM_COLUMNS}
100
+ contentContainerStyle={gridStyles.grid}
101
+ columnWrapperStyle={gridStyles.row}
102
+ renderItem={({ item }) => (
103
+ <View style={gridStyles.cell}>
104
+ <YearPill year={item.id} yearStyles={yearStyle} />
105
+ </View>
106
+ )}
107
+ keyExtractor={item => `${item.id}`}
108
+ getItemLayout={getRowLayout}
109
+ initialScrollIndex={Math.floor(initialScrollOffset / GRID_ITEM_HEIGHT)}
110
+ removeClippedSubviews
111
+ />
112
+ </View>
113
+ </List>
84
114
  );
85
115
  }
86
116
 
87
- function YearPure({
88
- year,
89
- selected,
90
- onPressYear,
91
- yearStyles,
92
- }: {
93
- year: number;
94
- selected: boolean;
95
- onPressYear: (newYear: number) => any;
96
- yearStyles: Record<string, any>;
97
- }) {
117
+ function YearPillPure({ year, yearStyles }: { year: number; yearStyles: Record<string, any> }) {
118
+ const isSelected = useListContextValue(state => {
119
+ const selectedValue = state.value as any;
120
+ return (selectedValue?.id ?? selectedValue) === year;
121
+ });
122
+
98
123
  datePickerYearItemStyles.useVariants({
99
- state: resolveStateVariant({
100
- selected,
101
- }) as any,
124
+ state: resolveStateVariant({ selected: isSelected }) as any,
102
125
  });
103
126
 
104
- const handlePressYear = useCallback(() => {
105
- onPressYear(year);
106
- }, [year, onPressYear]);
127
+ return (
128
+ <List.Item
129
+ value={year}
130
+ accessibilityRole="button"
131
+ accessibilityLabel={String(year)}
132
+ style={[yearStyles, datePickerYearItemStyles.yearButton]}
133
+ testID={`pick-year-${year}`}>
134
+ <View style={datePickerYearItemStyles.content}>
135
+ <Text
136
+ typescale="bodyLarge"
137
+ style={datePickerYearItemStyles.yearLabel}
138
+ selectable={false}>
139
+ {year}
140
+ </Text>
141
+ </View>
142
+ </List.Item>
143
+ );
144
+ }
145
+ const YearPill = memo(YearPillPure);
146
+
147
+ function YearPickerList() {
148
+ const [{ startDateYear, endDateYear, localDate, pickerType }, setStore] =
149
+ useDatePickerInlineStore(state => state);
150
+ const years = useMemo(
151
+ () => getYearRange(startDateYear, endDateYear),
152
+ [startDateYear, endDateYear],
153
+ );
154
+ const yearItems = useMemo<YearListItem[]>(
155
+ () => years.map(year => ({ id: year, label: String(year) })),
156
+ [years],
157
+ );
158
+ const selectingYear = pickerType === 'year';
159
+ const selectedYear = localDate.getFullYear();
160
+
161
+ const initialScrollIndex = useMemo(() => {
162
+ if (years.length === 0) return 0;
163
+ const idx = years.indexOf(selectedYear);
164
+ return Math.max(0, idx);
165
+ }, [selectedYear, years]);
166
+
167
+ const handleOnChange = useCallback(
168
+ (value: ListItemId | null) => {
169
+ if (typeof value !== 'number') return;
170
+ setStore(prev => ({
171
+ localDate: setYear(prev.localDate, value),
172
+ pickerType: undefined,
173
+ }));
174
+ },
175
+ [setStore],
176
+ );
177
+
178
+ const getItemLayout = useCallback(
179
+ (_data: ArrayLike<YearListItem> | null | undefined, index: number) => ({
180
+ length: LIST_ITEM_HEIGHT,
181
+ offset: LIST_ITEM_HEIGHT * index,
182
+ index,
183
+ }),
184
+ [],
185
+ );
186
+
187
+ if (!selectingYear) return null;
107
188
 
108
189
  return (
109
- <ListItem
110
- onPress={handlePressYear}
190
+ <List multiple={false} value={selectedYear} onChange={handleOnChange}>
191
+ <View style={[StyleSheet.absoluteFill, listStyles.root]} pointerEvents="auto">
192
+ <Divider />
193
+ <FlatList<YearListItem>
194
+ style={listStyles.list}
195
+ data={yearItems}
196
+ renderItem={({ item }) => <YearRow year={item.id} />}
197
+ keyExtractor={item => `${item.id}`}
198
+ initialScrollIndex={initialScrollIndex}
199
+ getItemLayout={getItemLayout}
200
+ />
201
+ </View>
202
+ </List>
203
+ );
204
+ }
205
+
206
+ function YearRowPure({ year }: { year: number }) {
207
+ const isSelected = useListContextValue(state => {
208
+ const selectedValue = state.value as any;
209
+ return (selectedValue?.id ?? selectedValue) === year;
210
+ });
211
+
212
+ datePickerMonthItemStyles.useVariants({
213
+ state: resolveStateVariant({ selected: isSelected }) as any,
214
+ });
215
+
216
+ return (
217
+ <List.Item
218
+ value={year}
111
219
  accessibilityRole="button"
112
220
  accessibilityLabel={String(year)}
113
- style={[datePickerYearItemStyles.yearButton, yearStyles]}
221
+ style={datePickerMonthItemStyles.monthButton}
114
222
  testID={`pick-year-${year}`}>
115
- <ListItem.Title style={datePickerYearItemStyles.yearLabel} selectable={false}>
116
- {year}
117
- </ListItem.Title>
118
- </ListItem>
223
+ <View style={listStyles.left}>
224
+ {isSelected ? (
225
+ <View style={listStyles.checkIconView}>
226
+ <Icon name="check" size={24} />
227
+ </View>
228
+ ) : (
229
+ <View style={listStyles.spacer} />
230
+ )}
231
+ </View>
232
+ <View style={datePickerMonthItemStyles.monthInner}>
233
+ <Text style={datePickerMonthItemStyles.monthLabel} selectable={false}>
234
+ {year}
235
+ </Text>
236
+ </View>
237
+ </List.Item>
119
238
  );
120
239
  }
121
- const Year = memo(YearPure);
240
+ const YearRow = memo(YearRowPure);
122
241
 
123
- const styles = StyleSheet.create({
242
+ const gridStyles = StyleSheet.create({
124
243
  root: {
125
244
  flex: 1,
126
245
  top: 56,
127
246
  zIndex: 100,
128
247
  },
129
-
130
248
  list: {
131
249
  flex: 1,
250
+ width: '100%',
251
+ },
252
+ grid: { alignItems: 'center' },
253
+ row: {
254
+ flexDirection: 'row',
255
+ justifyContent: 'center',
256
+ alignSelf: 'stretch',
257
+ gap: 12,
132
258
  },
133
- opacity0: {
134
- opacity: 0,
259
+ cell: {
260
+ flex: 1,
261
+ maxWidth: 110,
262
+ minWidth: 90,
263
+ height: GRID_ITEM_HEIGHT,
264
+ justifyContent: 'center',
135
265
  },
136
- opacity1: {
137
- opacity: 1,
266
+ opacity0: { opacity: 0 },
267
+ opacity1: { opacity: 1 },
268
+ });
269
+
270
+ const listStyles = StyleSheet.create({
271
+ root: {
272
+ flex: 1,
273
+ top: 56,
274
+ zIndex: 100,
275
+ backgroundColor: datePickerMonthPickerStyles.root.backgroundColor,
138
276
  },
277
+ list: { flex: 1 },
278
+ checkIconView: { marginLeft: 16 },
279
+ spacer: { width: 44 },
280
+ left: { marginRight: 8 },
139
281
  });