react-native-molecules 0.5.0-beta.2 → 0.5.0-beta.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/components/Accordion/Accordion.tsx +2 -6
- package/components/Accordion/AccordionItem.tsx +16 -12
- package/components/Accordion/AccordionItemContent.tsx +6 -1
- package/components/Accordion/AccordionItemHeader.tsx +1 -1
- package/components/Accordion/utils.ts +6 -0
- package/components/ActivityIndicator/ActivityIndicator.tsx +6 -15
- package/components/Appbar/AppbarBase.tsx +18 -13
- package/components/Button/Button.tsx +209 -264
- package/components/Button/index.tsx +9 -3
- package/components/Button/types.ts +16 -2
- package/components/Button/utils.ts +230 -208
- package/components/Checkbox/CheckboxBase.tsx +23 -128
- package/components/Checkbox/utils.ts +0 -25
- package/components/Chip/Chip.tsx +40 -52
- package/components/Chip/utils.ts +3 -7
- package/components/DateField/DateField.tsx +110 -0
- package/components/DateField/index.tsx +6 -0
- package/components/{DatePickerInput/inputUtils.ts → DateField/useDateFieldState.ts} +17 -49
- package/components/DatePicker/DateCalendar.tsx +83 -0
- package/components/DatePicker/DatePickerActions.tsx +73 -0
- package/components/DatePicker/DatePickerModal.tsx +234 -0
- package/components/DatePicker/DatePickerPopover.tsx +79 -0
- package/components/DatePicker/DatePickerProvider.tsx +152 -0
- package/components/DatePicker/DatePickerTrigger.tsx +23 -0
- package/components/DatePicker/context.tsx +82 -0
- package/components/DatePicker/index.tsx +44 -0
- package/components/DatePicker/utils.ts +293 -0
- package/components/DatePickerInline/DatePickerContext.tsx +1 -0
- package/components/DatePickerInline/DatePickerDockedHeader.tsx +113 -0
- package/components/DatePickerInline/DatePickerInline.tsx +16 -15
- package/components/DatePickerInline/DatePickerInlineBase.tsx +7 -1
- package/components/DatePickerInline/Day.tsx +25 -1
- package/components/DatePickerInline/DayRange.tsx +2 -4
- package/components/DatePickerInline/HeaderItem.tsx +42 -27
- package/components/DatePickerInline/Month.tsx +45 -65
- package/components/DatePickerInline/MonthPicker.tsx +25 -41
- package/components/DatePickerInline/Swiper.native.tsx +21 -4
- package/components/DatePickerInline/Swiper.tsx +168 -13
- package/components/DatePickerInline/Week.tsx +6 -1
- package/components/DatePickerInline/YearPicker.tsx +206 -53
- package/components/DatePickerInline/dateUtils.tsx +17 -12
- package/components/DatePickerInline/types.ts +3 -0
- package/components/DatePickerInline/utils.ts +66 -29
- package/components/Drawer/Drawer.tsx +17 -6
- package/components/ElementGroup/ElementGroup.tsx +16 -14
- package/components/FilePicker/FilePicker.tsx +48 -78
- package/components/FilePicker/index.tsx +2 -1
- package/components/FilePicker/utils.ts +9 -0
- package/components/HelperText/HelperText.tsx +0 -35
- package/components/Icon/iconFactory.tsx +3 -3
- package/components/Icon/index.tsx +1 -1
- package/components/Icon/types.ts +17 -6
- package/components/IconButton/IconButton.tsx +42 -57
- package/components/IconButton/utils.ts +142 -33
- package/components/ListItem/ListItem.tsx +3 -1
- package/components/ListItem/utils.ts +1 -1
- package/components/LoadingIndicator/LoadingIndicator.tsx +253 -0
- package/components/LoadingIndicator/LoadingIndicator.web.tsx +136 -0
- package/components/LoadingIndicator/index.tsx +13 -0
- package/components/LoadingIndicator/utils.ts +117 -0
- package/components/Menu/Menu.tsx +3 -18
- package/components/NavigationRail/NavigationRail.tsx +15 -9
- package/components/Popover/Popover.tsx +122 -145
- package/components/Popover/PopoverRoot.tsx +74 -0
- package/components/Popover/common.ts +50 -34
- package/components/Popover/index.ts +18 -1
- package/components/Popover/usePlatformMeasure.native.ts +90 -0
- package/components/Popover/usePlatformMeasure.ts +118 -0
- package/components/Popover/utils.ts +34 -0
- package/components/Select/Select.tsx +368 -507
- package/components/Select/context.tsx +72 -0
- package/components/Select/index.ts +8 -14
- package/components/Select/types.ts +2 -4
- package/components/Select/utils.ts +144 -0
- package/components/Slot/Slot.tsx +244 -0
- package/components/Slot/compose-refs.tsx +62 -0
- package/components/Slot/index.tsx +8 -0
- package/components/Surface/Surface.android.tsx +34 -8
- package/components/Surface/Surface.ios.tsx +36 -29
- package/components/Surface/Surface.tsx +31 -4
- package/components/Surface/utils.ts +44 -30
- package/components/Switch/Switch.tsx +8 -2
- package/components/Tabs/TabItem.tsx +35 -58
- package/components/Tabs/TabLabel.tsx +5 -9
- package/components/Tabs/Tabs.tsx +154 -148
- package/components/Tabs/utils.ts +15 -2
- package/components/TextInput/TextInput.tsx +658 -575
- package/components/TextInput/index.tsx +19 -3
- package/components/TextInput/types.ts +76 -27
- package/components/TextInput/utils.ts +225 -145
- package/components/TimeField/TimeField.tsx +75 -0
- package/components/TimeField/index.tsx +6 -0
- package/components/TimeField/useTimeFieldState.ts +70 -0
- package/components/{TimePickerField/sanitizeTime.ts → TimeField/utils.ts} +77 -10
- package/components/TimePicker/TimeInput.tsx +87 -37
- package/components/TimePicker/TimeInputs.tsx +137 -49
- package/components/TimePicker/TimePicker.tsx +73 -10
- package/components/TimePicker/TimePickerModal.tsx +186 -0
- package/components/TimePicker/context.tsx +17 -0
- package/components/TimePicker/index.tsx +15 -3
- package/components/TimePicker/utils.ts +93 -0
- package/components/Tooltip/Tooltip.tsx +42 -67
- package/components/Tooltip/TooltipContent.tsx +32 -5
- package/components/Tooltip/TooltipTrigger.tsx +20 -20
- package/components/Tooltip/index.tsx +1 -1
- package/components/TouchableRipple/TouchableRipple.native.tsx +50 -14
- package/components/TouchableRipple/TouchableRipple.tsx +137 -47
- package/hocs/withPortal.tsx +1 -1
- package/hooks/index.tsx +0 -6
- package/hooks/useActionState.tsx +19 -8
- package/hooks/useControlledValue.tsx +20 -4
- package/hooks/useFilePicker.tsx +6 -16
- package/hooks/useWhatHasUpdated.tsx +48 -0
- package/package.json +17 -13
- package/shortcuts-manager/ShortcutsManager/ShortcutsManager.tsx +5 -2
- package/styles/shadow.ts +2 -1
- package/styles/themes/LightTheme.tsx +1 -1
- package/utils/DocumentPicker/documentPicker.ts +78 -27
- package/utils/DocumentPicker/types.ts +0 -1
- package/utils/extractPropertiesFromStyles.ts +25 -0
- package/utils/extractSubcomponents.ts +89 -0
- package/utils/lodash.ts +77 -5
- package/components/DatePickerDocked/DatePickerDocked.tsx +0 -30
- package/components/DatePickerDocked/DatePickerDockedHeader.tsx +0 -129
- package/components/DatePickerDocked/index.tsx +0 -17
- package/components/DatePickerDocked/types.ts +0 -11
- package/components/DatePickerDocked/utils.ts +0 -157
- package/components/DatePickerInput/DatePickerInput.tsx +0 -139
- package/components/DatePickerInput/DatePickerInputModal.tsx +0 -48
- package/components/DatePickerInput/DatePickerInputWithoutModal.tsx +0 -77
- package/components/DatePickerInput/DateRangeInput.tsx +0 -88
- package/components/DatePickerInput/index.tsx +0 -10
- package/components/DatePickerInput/types.ts +0 -28
- package/components/DatePickerInput/utils.ts +0 -15
- package/components/DatePickerModal/AnimatedCrossView.tsx +0 -94
- package/components/DatePickerModal/CalendarEdit.tsx +0 -139
- package/components/DatePickerModal/DatePickerModal.tsx +0 -85
- package/components/DatePickerModal/DatePickerModalContent.tsx +0 -155
- package/components/DatePickerModal/DatePickerModalContentHeader.tsx +0 -213
- package/components/DatePickerModal/DatePickerModalHeader.tsx +0 -74
- package/components/DatePickerModal/DatePickerModalHeaderBackground.tsx +0 -13
- package/components/DatePickerModal/index.tsx +0 -16
- package/components/DatePickerModal/types.ts +0 -92
- package/components/DatePickerModal/utils.ts +0 -122
- package/components/DateTimePicker/DateTimePicker.tsx +0 -172
- package/components/DateTimePicker/index.tsx +0 -10
- package/components/DateTimePicker/utils.ts +0 -12
- package/components/Popover/Popover.native.tsx +0 -185
- package/components/TimePickerField/TimePickerField.tsx +0 -152
- package/components/TimePickerField/index.tsx +0 -10
- package/components/TimePickerField/utils.ts +0 -94
- package/components/TimePickerModal/TimePickerModal.tsx +0 -115
- package/components/TimePickerModal/index.tsx +0 -10
- package/components/TimePickerModal/utils.ts +0 -47
- package/hooks/useSearchable.tsx +0 -74
- package/hooks/useSubcomponents.tsx +0 -59
|
@@ -9,11 +9,12 @@ import {
|
|
|
9
9
|
useRef,
|
|
10
10
|
useState,
|
|
11
11
|
} from 'react';
|
|
12
|
-
import { StyleSheet, View } from 'react-native';
|
|
13
12
|
|
|
14
13
|
import { useLatest } from '../../hooks';
|
|
15
14
|
import AutoSizer from './AutoSizer';
|
|
16
|
-
import {
|
|
15
|
+
import { useDatePickerStore } from './DatePickerContext';
|
|
16
|
+
import { beginOffset, estimatedMonthHeight, getInitialIndex, totalMonths } from './dateUtils';
|
|
17
|
+
import { addMonths, getRealIndex } from './dateUtils';
|
|
17
18
|
import { getIndexFromVerticalOffset, getMonthHeight, getVerticalMonthsOffset } from './Month';
|
|
18
19
|
import type { SwiperProps } from './SwiperUtils';
|
|
19
20
|
import { montHeaderHeight } from './utils';
|
|
@@ -25,7 +26,16 @@ function Swiper({ scrollMode, renderItem, renderHeader, renderFooter, initialInd
|
|
|
25
26
|
<>
|
|
26
27
|
{renderHeader && renderHeader()}
|
|
27
28
|
{isHorizontal ? (
|
|
28
|
-
<
|
|
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] = useDatePickerStore(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] = useDatePickerStore(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);
|
|
@@ -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
|
-
{
|
|
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,48 +1,70 @@
|
|
|
1
1
|
import { setYear } from 'date-fns';
|
|
2
|
-
import { memo, useCallback,
|
|
3
|
-
import { FlatList, StyleSheet, View } from 'react-native';
|
|
2
|
+
import { memo, useCallback, useLayoutEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import { FlatList, ScrollView, StyleSheet, View } from 'react-native';
|
|
4
4
|
|
|
5
5
|
import { getYearRange, resolveStateVariant } from '../../utils';
|
|
6
|
+
import { datePickerMonthItemStyles, datePickerMonthPickerStyles } from '../DatePicker/utils';
|
|
6
7
|
import { HorizontalDivider } from '../HorizontalDivider';
|
|
8
|
+
import { Icon } from '../Icon';
|
|
7
9
|
import { ListItem } from '../ListItem';
|
|
10
|
+
import { Text } from '../Text';
|
|
8
11
|
import { useDatePickerStore } from './DatePickerContext';
|
|
9
12
|
import { datePickerYearItemStyles, datePickerYearPickerStyles } from './utils';
|
|
10
13
|
|
|
11
|
-
const
|
|
14
|
+
const GRID_ITEM_HEIGHT = 62;
|
|
15
|
+
const NUM_COLUMNS = 3;
|
|
16
|
+
const LIST_ITEM_HEIGHT = 46;
|
|
12
17
|
|
|
13
|
-
|
|
18
|
+
type YearPickerProps = {
|
|
19
|
+
layout?: 'grid' | 'list';
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default function YearPicker({ layout = 'grid' }: YearPickerProps) {
|
|
23
|
+
if (layout === 'list') return <YearPickerList />;
|
|
24
|
+
return <YearPickerGrid />;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function YearPickerGrid() {
|
|
14
28
|
const [{ startDateYear, endDateYear, localDate, pickerType }, setStore] = useDatePickerStore(
|
|
15
29
|
state => state,
|
|
16
30
|
);
|
|
17
|
-
const flatList = useRef<FlatList<number> | null>(null);
|
|
18
31
|
const years = useMemo(
|
|
19
32
|
() => getYearRange(startDateYear, endDateYear),
|
|
20
33
|
[startDateYear, endDateYear],
|
|
21
34
|
);
|
|
35
|
+
const rows = useMemo(() => {
|
|
36
|
+
const chunks: number[][] = [];
|
|
37
|
+
for (let i = 0; i < years.length; i += NUM_COLUMNS) {
|
|
38
|
+
chunks.push(years.slice(i, i + NUM_COLUMNS));
|
|
39
|
+
}
|
|
40
|
+
return chunks;
|
|
41
|
+
}, [years]);
|
|
22
42
|
const selectingYear = pickerType === 'year';
|
|
23
43
|
const selectedYear = localDate.getFullYear();
|
|
44
|
+
const scrollRef = useRef<ScrollView | null>(null);
|
|
24
45
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
46
|
+
const initialScrollOffset = useMemo(() => {
|
|
47
|
+
if (years.length === 0) return 0;
|
|
48
|
+
const totalRows = Math.ceil(years.length / NUM_COLUMNS);
|
|
49
|
+
const unclampedRow = Math.floor((selectedYear - years[0]) / NUM_COLUMNS);
|
|
50
|
+
const rowIndex = Math.min(Math.max(0, unclampedRow), Math.max(0, totalRows - 1));
|
|
51
|
+
return rowIndex * GRID_ITEM_HEIGHT;
|
|
52
|
+
}, [selectedYear, years]);
|
|
53
|
+
|
|
54
|
+
useLayoutEffect(() => {
|
|
55
|
+
if (!selectingYear) return;
|
|
56
|
+
scrollRef.current?.scrollTo({ y: initialScrollOffset, animated: false });
|
|
57
|
+
}, [selectingYear, initialScrollOffset]);
|
|
35
58
|
|
|
36
59
|
const { containerStyle, yearStyle } = useMemo(() => {
|
|
37
60
|
const { backgroundColor, ...rest } = datePickerYearPickerStyles.root;
|
|
38
|
-
|
|
39
61
|
return {
|
|
40
62
|
containerStyle: [
|
|
41
63
|
StyleSheet.absoluteFill,
|
|
42
|
-
|
|
64
|
+
gridStyles.root,
|
|
43
65
|
{ backgroundColor },
|
|
44
66
|
datePickerYearPickerStyles.yearContainer,
|
|
45
|
-
selectingYear ?
|
|
67
|
+
selectingYear ? gridStyles.opacity1 : gridStyles.opacity0,
|
|
46
68
|
],
|
|
47
69
|
yearStyle: rest,
|
|
48
70
|
};
|
|
@@ -59,32 +81,39 @@ export default function YearPicker() {
|
|
|
59
81
|
);
|
|
60
82
|
|
|
61
83
|
return (
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
84
|
+
<View style={containerStyle} pointerEvents={selectingYear ? 'auto' : 'none'}>
|
|
85
|
+
<HorizontalDivider />
|
|
86
|
+
<ScrollView
|
|
87
|
+
ref={scrollRef}
|
|
88
|
+
style={gridStyles.list}
|
|
89
|
+
contentOffset={{ x: 0, y: initialScrollOffset }}
|
|
90
|
+
removeClippedSubviews>
|
|
91
|
+
<View style={gridStyles.grid}>
|
|
92
|
+
{rows.map((row, rowIdx) => (
|
|
93
|
+
<View key={rowIdx} style={gridStyles.row}>
|
|
94
|
+
{row.map(year => (
|
|
95
|
+
<View key={year} style={gridStyles.cell}>
|
|
96
|
+
<YearPill
|
|
97
|
+
year={year}
|
|
98
|
+
selected={selectedYear === year}
|
|
99
|
+
onPressYear={handleOnChange}
|
|
100
|
+
yearStyles={yearStyle}
|
|
101
|
+
/>
|
|
102
|
+
</View>
|
|
103
|
+
))}
|
|
104
|
+
{row.length < NUM_COLUMNS &&
|
|
105
|
+
Array.from({ length: NUM_COLUMNS - row.length }).map((_, i) => (
|
|
106
|
+
<View key={`pad-${i}`} style={gridStyles.cell} />
|
|
107
|
+
))}
|
|
108
|
+
</View>
|
|
109
|
+
))}
|
|
81
110
|
</View>
|
|
82
|
-
|
|
83
|
-
|
|
111
|
+
</ScrollView>
|
|
112
|
+
</View>
|
|
84
113
|
);
|
|
85
114
|
}
|
|
86
115
|
|
|
87
|
-
function
|
|
116
|
+
function YearPillPure({
|
|
88
117
|
year,
|
|
89
118
|
selected,
|
|
90
119
|
onPressYear,
|
|
@@ -96,9 +125,7 @@ function YearPure({
|
|
|
96
125
|
yearStyles: Record<string, any>;
|
|
97
126
|
}) {
|
|
98
127
|
datePickerYearItemStyles.useVariants({
|
|
99
|
-
state: resolveStateVariant({
|
|
100
|
-
selected,
|
|
101
|
-
}) as any,
|
|
128
|
+
state: resolveStateVariant({ selected }) as any,
|
|
102
129
|
});
|
|
103
130
|
|
|
104
131
|
const handlePressYear = useCallback(() => {
|
|
@@ -107,10 +134,11 @@ function YearPure({
|
|
|
107
134
|
|
|
108
135
|
return (
|
|
109
136
|
<ListItem
|
|
137
|
+
contentStyle={datePickerYearItemStyles.content}
|
|
110
138
|
onPress={handlePressYear}
|
|
111
139
|
accessibilityRole="button"
|
|
112
140
|
accessibilityLabel={String(year)}
|
|
113
|
-
style={[datePickerYearItemStyles.yearButton
|
|
141
|
+
style={[yearStyles, datePickerYearItemStyles.yearButton]}
|
|
114
142
|
testID={`pick-year-${year}`}>
|
|
115
143
|
<ListItem.Title style={datePickerYearItemStyles.yearLabel} selectable={false}>
|
|
116
144
|
{year}
|
|
@@ -118,22 +146,147 @@ function YearPure({
|
|
|
118
146
|
</ListItem>
|
|
119
147
|
);
|
|
120
148
|
}
|
|
121
|
-
const
|
|
149
|
+
const YearPill = memo(YearPillPure);
|
|
150
|
+
|
|
151
|
+
function YearPickerList() {
|
|
152
|
+
const [{ startDateYear, endDateYear, localDate, pickerType }, setStore] = useDatePickerStore(
|
|
153
|
+
state => state,
|
|
154
|
+
);
|
|
155
|
+
const flatList = useRef<FlatList<number> | null>(null);
|
|
156
|
+
const years = useMemo(
|
|
157
|
+
() => getYearRange(startDateYear, endDateYear),
|
|
158
|
+
[startDateYear, endDateYear],
|
|
159
|
+
);
|
|
160
|
+
const selectingYear = pickerType === 'year';
|
|
161
|
+
const selectedYear = localDate.getFullYear();
|
|
162
|
+
|
|
163
|
+
const initialScrollIndex = useMemo(() => {
|
|
164
|
+
if (years.length === 0) return 0;
|
|
165
|
+
const idx = years.indexOf(selectedYear);
|
|
166
|
+
return Math.max(0, idx);
|
|
167
|
+
}, [selectedYear, years]);
|
|
168
|
+
|
|
169
|
+
const handleOnChange = useCallback(
|
|
170
|
+
(year: number) => {
|
|
171
|
+
setStore(prev => ({
|
|
172
|
+
localDate: setYear(prev.localDate, year),
|
|
173
|
+
pickerType: undefined,
|
|
174
|
+
}));
|
|
175
|
+
},
|
|
176
|
+
[setStore],
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
const renderItem = useCallback(
|
|
180
|
+
({ item }: { item: number }) => (
|
|
181
|
+
<YearRow year={item} selected={selectedYear === item} onPressYear={handleOnChange} />
|
|
182
|
+
),
|
|
183
|
+
[selectedYear, handleOnChange],
|
|
184
|
+
);
|
|
122
185
|
|
|
123
|
-
const
|
|
186
|
+
const getItemLayout = useCallback(
|
|
187
|
+
(_data: any, index: number) => ({
|
|
188
|
+
length: LIST_ITEM_HEIGHT,
|
|
189
|
+
offset: LIST_ITEM_HEIGHT * index,
|
|
190
|
+
index,
|
|
191
|
+
}),
|
|
192
|
+
[],
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
if (!selectingYear) return null;
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<View style={[StyleSheet.absoluteFill, listStyles.root]} pointerEvents="auto">
|
|
199
|
+
<HorizontalDivider />
|
|
200
|
+
<FlatList<number>
|
|
201
|
+
ref={flatList}
|
|
202
|
+
style={listStyles.list}
|
|
203
|
+
data={years}
|
|
204
|
+
renderItem={renderItem}
|
|
205
|
+
keyExtractor={item => `${item}`}
|
|
206
|
+
initialScrollIndex={initialScrollIndex}
|
|
207
|
+
getItemLayout={getItemLayout}
|
|
208
|
+
/>
|
|
209
|
+
</View>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function YearRowPure({
|
|
214
|
+
year,
|
|
215
|
+
selected,
|
|
216
|
+
onPressYear,
|
|
217
|
+
}: {
|
|
218
|
+
year: number;
|
|
219
|
+
selected: boolean;
|
|
220
|
+
onPressYear: (newYear: number) => any;
|
|
221
|
+
}) {
|
|
222
|
+
datePickerMonthItemStyles.useVariants({
|
|
223
|
+
state: resolveStateVariant({ selected }) as any,
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const handlePressYear = useCallback(() => {
|
|
227
|
+
onPressYear(year);
|
|
228
|
+
}, [year, onPressYear]);
|
|
229
|
+
|
|
230
|
+
return (
|
|
231
|
+
<ListItem
|
|
232
|
+
onPress={handlePressYear}
|
|
233
|
+
accessibilityRole="button"
|
|
234
|
+
accessibilityLabel={String(year)}
|
|
235
|
+
accessibilityState={{ selected }}
|
|
236
|
+
style={datePickerMonthItemStyles.monthButton}
|
|
237
|
+
testID={`pick-year-${year}`}
|
|
238
|
+
left={
|
|
239
|
+
selected ? (
|
|
240
|
+
<View style={listStyles.checkIconView}>
|
|
241
|
+
<Icon name="check" size={24} />
|
|
242
|
+
</View>
|
|
243
|
+
) : (
|
|
244
|
+
<View style={listStyles.spacer} />
|
|
245
|
+
)
|
|
246
|
+
}>
|
|
247
|
+
<View style={datePickerMonthItemStyles.monthInner}>
|
|
248
|
+
<Text style={datePickerMonthItemStyles.monthLabel} selectable={false}>
|
|
249
|
+
{year}
|
|
250
|
+
</Text>
|
|
251
|
+
</View>
|
|
252
|
+
</ListItem>
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
const YearRow = memo(YearRowPure);
|
|
256
|
+
|
|
257
|
+
const gridStyles = StyleSheet.create({
|
|
124
258
|
root: {
|
|
125
259
|
flex: 1,
|
|
126
260
|
top: 56,
|
|
127
261
|
zIndex: 100,
|
|
128
262
|
},
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
263
|
+
list: { flex: 1 },
|
|
264
|
+
grid: { alignItems: 'center' },
|
|
265
|
+
row: {
|
|
266
|
+
flexDirection: 'row',
|
|
267
|
+
justifyContent: 'center',
|
|
268
|
+
alignSelf: 'stretch',
|
|
269
|
+
gap: 12,
|
|
132
270
|
},
|
|
133
|
-
|
|
134
|
-
|
|
271
|
+
cell: {
|
|
272
|
+
flex: 1,
|
|
273
|
+
maxWidth: 110,
|
|
274
|
+
minWidth: 90,
|
|
275
|
+
height: GRID_ITEM_HEIGHT,
|
|
276
|
+
justifyContent: 'center',
|
|
135
277
|
},
|
|
136
|
-
|
|
137
|
-
|
|
278
|
+
opacity0: { opacity: 0 },
|
|
279
|
+
opacity1: { opacity: 1 },
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const listStyles = StyleSheet.create({
|
|
283
|
+
root: {
|
|
284
|
+
flex: 1,
|
|
285
|
+
top: 56,
|
|
286
|
+
zIndex: 100,
|
|
287
|
+
backgroundColor: datePickerMonthPickerStyles.root.backgroundColor,
|
|
138
288
|
},
|
|
289
|
+
list: { flex: 1 },
|
|
290
|
+
checkIconView: { marginLeft: 16 },
|
|
291
|
+
spacer: { width: 44 },
|
|
139
292
|
});
|
|
@@ -219,21 +219,25 @@ export const generateCalendarGrid = ({
|
|
|
219
219
|
const isFirstWeek = weekGrid === 0;
|
|
220
220
|
const realDayIndex = emptyDays - dayIndex;
|
|
221
221
|
const beforeWeekDay = isFirstWeek && realDayIndex > 0;
|
|
222
|
-
const
|
|
223
|
-
const afterWeekDay =
|
|
224
|
-
|
|
225
|
-
|
|
222
|
+
const rawDayOfMonth = weekGrid * 7 + dayIndex - emptyDays + 1;
|
|
223
|
+
const afterWeekDay = rawDayOfMonth > daysInMonth;
|
|
224
|
+
const outside = beforeWeekDay || afterWeekDay;
|
|
225
|
+
|
|
226
|
+
const day = new Date(year, month, rawDayOfMonth);
|
|
227
|
+
const dayOfMonth = day.getDate();
|
|
228
|
+
const displayYear = day.getFullYear();
|
|
229
|
+
const displayMonth = day.getMonth();
|
|
226
230
|
const isToday = areDatesOnSameDay(day, today);
|
|
227
231
|
|
|
228
232
|
let inRange = false;
|
|
229
233
|
let disabled = isDisabled(day);
|
|
230
234
|
let selected = false;
|
|
231
235
|
|
|
232
|
-
let leftCrop =
|
|
233
|
-
let rightCrop =
|
|
236
|
+
let leftCrop = rawDayOfMonth === 1;
|
|
237
|
+
let rightCrop = rawDayOfMonth === daysInMonth;
|
|
234
238
|
|
|
235
|
-
const isFirstDayOfMonth =
|
|
236
|
-
const isLastDayOfMonth =
|
|
239
|
+
const isFirstDayOfMonth = rawDayOfMonth === 1;
|
|
240
|
+
const isLastDayOfMonth = rawDayOfMonth === daysInMonth;
|
|
237
241
|
|
|
238
242
|
if (mode === 'range') {
|
|
239
243
|
const selectedStartDay = areDatesOnSameDay(day, startDate);
|
|
@@ -267,8 +271,8 @@ export const generateCalendarGrid = ({
|
|
|
267
271
|
const safeDates = dates || [];
|
|
268
272
|
selected = safeDates.some(d => areDatesOnSameDay(day, d));
|
|
269
273
|
|
|
270
|
-
const yesterday = new Date(year, month,
|
|
271
|
-
const tomorrow = new Date(year, month,
|
|
274
|
+
const yesterday = new Date(year, month, rawDayOfMonth - 1);
|
|
275
|
+
const tomorrow = new Date(year, month, rawDayOfMonth + 1);
|
|
272
276
|
|
|
273
277
|
const yesterdaySelected = safeDates.some(d => areDatesOnSameDay(d, yesterday));
|
|
274
278
|
const tomorrowSelected = safeDates.some(d => areDatesOnSameDay(d, tomorrow));
|
|
@@ -316,8 +320,9 @@ export const generateCalendarGrid = ({
|
|
|
316
320
|
return {
|
|
317
321
|
beforeWeekDay,
|
|
318
322
|
afterWeekDay,
|
|
319
|
-
|
|
320
|
-
|
|
323
|
+
outside,
|
|
324
|
+
year: displayYear,
|
|
325
|
+
month: displayMonth,
|
|
321
326
|
dayOfMonth,
|
|
322
327
|
dayIndex,
|
|
323
328
|
mode,
|
|
@@ -13,6 +13,7 @@ export interface BaseMonthProps {
|
|
|
13
13
|
onPressDate: (date: Date) => any;
|
|
14
14
|
validRange?: ValidRangeType;
|
|
15
15
|
customMonthStyles?: Record<string, any>;
|
|
16
|
+
showOutsideDays?: boolean;
|
|
16
17
|
|
|
17
18
|
// some of these should be required in final implementation
|
|
18
19
|
date?: CalendarDate;
|
|
@@ -52,7 +53,9 @@ export type BaseDatePickerProps = {
|
|
|
52
53
|
startYear?: number;
|
|
53
54
|
endYear?: number;
|
|
54
55
|
HeaderComponent?: MemoExoticComponent<CalendarHeaderProps | any>;
|
|
56
|
+
headerLayout?: 'inline' | 'docked';
|
|
55
57
|
monthStyle?: Record<string, any>;
|
|
58
|
+
showOutsideDays?: boolean;
|
|
56
59
|
|
|
57
60
|
// here they are optional but in final implemenation they are required
|
|
58
61
|
date?: CalendarDate;
|