react-native-molecules 0.5.0-beta.16 → 0.5.0-beta.18
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/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 +292 -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/ListItem/ListItem.tsx +3 -1
- package/components/ListItem/utils.ts +1 -1
- package/components/LoadingIndicator/index.tsx +1 -1
- package/components/Menu/Menu.tsx +3 -18
- 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 +7 -8
- package/components/Select/context.tsx +72 -0
- package/components/Select/index.ts +1 -0
- package/components/Select/utils.ts +0 -71
- package/components/Slot/compose-refs.tsx +2 -0
- 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/TimePicker.tsx +53 -9
- 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 +50 -0
- package/hooks/useActionState.tsx +19 -8
- package/package.json +6 -1
- 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 -130
- package/components/DatePickerInput/DatePickerInputModal.tsx +0 -48
- package/components/DatePickerInput/DatePickerInputWithoutModal.tsx +0 -73
- package/components/DatePickerInput/DateRangeInput.tsx +0 -88
- package/components/DatePickerInput/index.tsx +0 -11
- package/components/DatePickerInput/types.ts +0 -26
- package/components/DatePickerInput/utils.ts +0 -24
- package/components/DatePickerModal/AnimatedCrossView.tsx +0 -94
- package/components/DatePickerModal/CalendarEdit.tsx +0 -140
- 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 -154
- package/components/TimePickerField/index.tsx +0 -10
- package/components/TimePickerField/utils.ts +0 -94
- package/components/TimePickerModal/TimePickerModal.tsx +0 -119
- package/components/TimePickerModal/index.tsx +0 -10
- package/components/TimePickerModal/utils.ts +0 -47
|
@@ -1,19 +1,61 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { format, parse, set } from 'date-fns';
|
|
2
|
+
|
|
3
|
+
export const timeMask24Hour = (text: string = '') => {
|
|
4
|
+
const cleanTime = text.replace(/\D+/g, '');
|
|
5
|
+
|
|
6
|
+
const hourFirstDigit = /[012]/;
|
|
7
|
+
let hourSecondDigit = /\d/;
|
|
8
|
+
|
|
9
|
+
if (cleanTime.charAt(0) === '2') {
|
|
10
|
+
hourSecondDigit = /[0123]/;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return [hourFirstDigit, hourSecondDigit, ':', /[012345]/, /\d/];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const timeMask12Hour = (text: string = '') => {
|
|
17
|
+
const cleanTime = text.replace(/\D+/g, '');
|
|
18
|
+
|
|
19
|
+
const hourFirstDigit = /[01]/;
|
|
20
|
+
let hourSecondDigit = /\d/;
|
|
21
|
+
|
|
22
|
+
if (cleanTime.charAt(0) === '1') {
|
|
23
|
+
hourSecondDigit = /[012]/;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return [hourFirstDigit, hourSecondDigit, ':', /[012345]/, /\d/, /[ap]/, 'm'];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const timeFormat = {
|
|
30
|
+
'24': {
|
|
31
|
+
format: 'HH:mm',
|
|
32
|
+
mask: timeMask24Hour,
|
|
33
|
+
},
|
|
34
|
+
'12': {
|
|
35
|
+
format: 'hh:mmaaa',
|
|
36
|
+
mask: timeMask12Hour,
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const referenceDate = new Date('2022-01-01T00:00:00.000Z');
|
|
41
|
+
|
|
42
|
+
export const sanitizeTimeString = (time: string): string => time.replace(/[^\d:]/g, '');
|
|
43
|
+
|
|
44
|
+
export const sanitizeTime = (value: string, is24Hour = false) => {
|
|
3
45
|
const sanitizedValue = value.replace(/[^0-9:apm]/gi, '');
|
|
4
46
|
|
|
5
47
|
const singleDigitHour = sanitizedValue.match(/^(\d{1,2})$/);
|
|
6
48
|
if (singleDigitHour) {
|
|
7
49
|
const hours = parseInt(singleDigitHour[1], 10);
|
|
8
|
-
if (!
|
|
50
|
+
if (!is24Hour && hours >= 1 && hours <= 12) {
|
|
9
51
|
return `${hours}:00am`;
|
|
10
|
-
}
|
|
52
|
+
}
|
|
53
|
+
if (is24Hour && hours >= 0 && hours < 24) {
|
|
11
54
|
return `${hours.toString().padStart(2, '0')}:00`;
|
|
12
55
|
}
|
|
13
56
|
}
|
|
14
57
|
|
|
15
|
-
if (
|
|
16
|
-
// Check if it's a valid 24-hour time format (HH:MM)
|
|
58
|
+
if (is24Hour) {
|
|
17
59
|
const match24Hour = sanitizedValue.match(/^(\d{1,2}):?(\d{2})$/);
|
|
18
60
|
|
|
19
61
|
if (match24Hour) {
|
|
@@ -27,7 +69,6 @@ export const sanitizeTime = (value: string, is24hour = false) => {
|
|
|
27
69
|
}
|
|
28
70
|
}
|
|
29
71
|
|
|
30
|
-
// Convert 12-hour time to 24-hour time format if necessary
|
|
31
72
|
const match12Hour = sanitizedValue.match(/^(\d{1,2}):?(\d{2})\s*([ap]m)$/i);
|
|
32
73
|
if (match12Hour) {
|
|
33
74
|
let hours = parseInt(match12Hour[1], 10);
|
|
@@ -40,15 +81,16 @@ export const sanitizeTime = (value: string, is24hour = false) => {
|
|
|
40
81
|
} else if (period === 'am' && hours === 12) {
|
|
41
82
|
hours = 0;
|
|
42
83
|
}
|
|
84
|
+
|
|
43
85
|
return `${hours.toString().padStart(2, '0')}:${minutes
|
|
44
86
|
.toString()
|
|
45
87
|
.padStart(2, '0')}`;
|
|
46
88
|
}
|
|
47
89
|
}
|
|
90
|
+
|
|
48
91
|
return '';
|
|
49
92
|
}
|
|
50
93
|
|
|
51
|
-
// Convert 24-hour time to 12-hour time format if necessary
|
|
52
94
|
const match24Hour = sanitizedValue.match(/^(\d{1,2}):?(\d{2})$/);
|
|
53
95
|
if (match24Hour) {
|
|
54
96
|
let hours = parseInt(match24Hour[1], 10);
|
|
@@ -68,7 +110,6 @@ export const sanitizeTime = (value: string, is24hour = false) => {
|
|
|
68
110
|
}
|
|
69
111
|
}
|
|
70
112
|
|
|
71
|
-
// Check if it's a valid 12-hour time format (HH:MM am/pm)
|
|
72
113
|
const match12Hour = sanitizedValue.match(/^(\d{1,2}):?(\d{2})\s*([ap]m)$/i);
|
|
73
114
|
if (match12Hour) {
|
|
74
115
|
const hours = parseInt(match12Hour[1], 10);
|
|
@@ -80,6 +121,32 @@ export const sanitizeTime = (value: string, is24hour = false) => {
|
|
|
80
121
|
}
|
|
81
122
|
}
|
|
82
123
|
|
|
83
|
-
// If no match, return empty string as invalid input
|
|
84
124
|
return '';
|
|
85
125
|
};
|
|
126
|
+
|
|
127
|
+
export const getFormattedTime = ({ time, is24Hour }: { time: string; is24Hour: boolean }) => {
|
|
128
|
+
if (!time) return '';
|
|
129
|
+
|
|
130
|
+
const [hour = '0', minute = '0'] = sanitizeTimeString(time).split(':');
|
|
131
|
+
|
|
132
|
+
return format(
|
|
133
|
+
set(referenceDate, { hours: +hour.padStart(2, '0'), minutes: +minute.padStart(2, '0') }),
|
|
134
|
+
timeFormat[is24Hour ? '24' : '12'].format,
|
|
135
|
+
);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
export const getOutputTime = ({ time, is24Hour }: { time: string; is24Hour: boolean }) => {
|
|
139
|
+
if (!time) return '';
|
|
140
|
+
|
|
141
|
+
const formattedTime = sanitizeTimeString(getFormattedTime({ time, is24Hour }));
|
|
142
|
+
const isPM = time.replace(/[\d:]/g, '').includes('p');
|
|
143
|
+
|
|
144
|
+
return format(
|
|
145
|
+
parse(
|
|
146
|
+
formattedTime + (is24Hour ? '' : isPM ? 'pm' : 'am'),
|
|
147
|
+
timeFormat[is24Hour ? '24' : '12'].format,
|
|
148
|
+
referenceDate,
|
|
149
|
+
),
|
|
150
|
+
'HH:mm',
|
|
151
|
+
);
|
|
152
|
+
};
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { memo, useCallback, useMemo, useState } from 'react';
|
|
2
2
|
import { type StyleProp, View, type ViewStyle } from 'react-native';
|
|
3
3
|
|
|
4
|
+
import { getRegisteredComponentWithFallback } from '../../core';
|
|
4
5
|
import { useControlledValue } from '../../hooks';
|
|
5
6
|
import { format, parse } from '../../utils/date-fns';
|
|
7
|
+
import { useOptionalDatePickerContext } from '../DatePicker/context';
|
|
6
8
|
import AnalogClock from './AnalogClock';
|
|
9
|
+
import { useOptionalTimePickerContext } from './context';
|
|
7
10
|
import { DisplayModeContext } from './DisplayModeContext';
|
|
8
11
|
import TimeInputs from './TimeInputs';
|
|
9
12
|
import {
|
|
@@ -26,10 +29,10 @@ type onChangeFunc = ({
|
|
|
26
29
|
|
|
27
30
|
export type Props = {
|
|
28
31
|
/**
|
|
29
|
-
* hh:mm format
|
|
32
|
+
* hh:mm format. Optional when mounted inside a DatePickerProvider — the provider's Date is read instead.
|
|
30
33
|
* */
|
|
31
|
-
time
|
|
32
|
-
onTimeChange
|
|
34
|
+
time?: string;
|
|
35
|
+
onTimeChange?: (params: { time: string; focused?: undefined | PossibleClockTypes }) => any;
|
|
33
36
|
|
|
34
37
|
is24Hour?: boolean;
|
|
35
38
|
inputType?: PossibleInputTypes;
|
|
@@ -40,16 +43,54 @@ export type Props = {
|
|
|
40
43
|
style?: StyleProp<ViewStyle>;
|
|
41
44
|
};
|
|
42
45
|
|
|
46
|
+
const toTimeString = (value: Date | null | undefined): string => {
|
|
47
|
+
if (!value) return '';
|
|
48
|
+
const h = value.getHours().toString().padStart(2, '0');
|
|
49
|
+
const m = value.getMinutes().toString().padStart(2, '0');
|
|
50
|
+
return `${h}:${m}`;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const applyTimeToDate = (base: Date | null | undefined, time: string): Date | null => {
|
|
54
|
+
if (!time) return null;
|
|
55
|
+
const [h, m] = time.split(':').map(n => parseInt(n, 10));
|
|
56
|
+
if (Number.isNaN(h) || Number.isNaN(m)) return base ?? null;
|
|
57
|
+
const next = base ? new Date(base) : new Date();
|
|
58
|
+
next.setHours(h, m, 0, 0);
|
|
59
|
+
return next;
|
|
60
|
+
};
|
|
61
|
+
|
|
43
62
|
function TimePicker({
|
|
44
|
-
|
|
45
|
-
|
|
63
|
+
time: timeProp,
|
|
64
|
+
onTimeChange: onTimeChangeProp,
|
|
65
|
+
is24Hour: is24HourProp,
|
|
46
66
|
focused: focusedProp,
|
|
47
67
|
onFocusInput: onFocusInputProp,
|
|
48
|
-
inputType
|
|
49
|
-
onTimeChange,
|
|
68
|
+
inputType: inputTypeProp,
|
|
50
69
|
isLandscape = false,
|
|
51
70
|
style,
|
|
52
71
|
}: Props) {
|
|
72
|
+
const ctx = useOptionalDatePickerContext();
|
|
73
|
+
const tpCtx = useOptionalTimePickerContext();
|
|
74
|
+
|
|
75
|
+
const ctxDate =
|
|
76
|
+
ctx && ctx.draftValue && typeof ctx.draftValue === 'object' && 'start' in ctx.draftValue
|
|
77
|
+
? null
|
|
78
|
+
: (ctx?.draftValue as Date | null | undefined);
|
|
79
|
+
|
|
80
|
+
const time = timeProp ?? toTimeString(ctxDate);
|
|
81
|
+
const is24Hour = is24HourProp ?? ctx?.is24Hour ?? false;
|
|
82
|
+
const inputType = inputTypeProp ?? tpCtx?.inputType ?? (ctx ? 'picker' : 'keyboard');
|
|
83
|
+
|
|
84
|
+
const onTimeChange = useMemo(
|
|
85
|
+
() =>
|
|
86
|
+
onTimeChangeProp ??
|
|
87
|
+
((params: { time: string; focused?: undefined | PossibleClockTypes }) => {
|
|
88
|
+
if (!ctx) return;
|
|
89
|
+
ctx.setValue(applyTimeToDate(ctxDate, params.time));
|
|
90
|
+
}),
|
|
91
|
+
[onTimeChangeProp, ctx, ctxDate],
|
|
92
|
+
);
|
|
93
|
+
|
|
53
94
|
const { hours, minutes } = useMemo(() => {
|
|
54
95
|
const date = time ? parse(time, 'HH:mm', new Date()) : new Date();
|
|
55
96
|
|
|
@@ -62,7 +103,6 @@ function TimePicker({
|
|
|
62
103
|
onChange: onFocusInputProp,
|
|
63
104
|
});
|
|
64
105
|
|
|
65
|
-
// Initialize display Mode according the hours value
|
|
66
106
|
const [displayMode, setDisplayMode] = useState<'AM' | 'PM' | undefined>(() =>
|
|
67
107
|
!is24Hour ? (hours >= 12 ? 'PM' : 'AM') : undefined,
|
|
68
108
|
);
|
|
@@ -127,4 +167,8 @@ function TimePicker({
|
|
|
127
167
|
);
|
|
128
168
|
}
|
|
129
169
|
|
|
130
|
-
|
|
170
|
+
const TimePickerDefault = memo(TimePicker);
|
|
171
|
+
|
|
172
|
+
export default TimePickerDefault;
|
|
173
|
+
|
|
174
|
+
export const TimePickerClock = getRegisteredComponentWithFallback('TimePicker', TimePickerDefault);
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { memo, useMemo } from 'react';
|
|
3
|
+
import { KeyboardAvoidingView, Platform, View } from 'react-native';
|
|
4
|
+
|
|
5
|
+
import { getRegisteredComponentWithFallback } from '../../core';
|
|
6
|
+
import { useControlledValue } from '../../hooks';
|
|
7
|
+
import { DatePickerActions, DatePickerProvider } from '../DatePicker';
|
|
8
|
+
import type { DatePickerContextType, DatePickerValue } from '../DatePicker/context';
|
|
9
|
+
import {
|
|
10
|
+
DatePickerContext,
|
|
11
|
+
useDatePickerContext,
|
|
12
|
+
useOptionalDatePickerContext,
|
|
13
|
+
withDraftLayer,
|
|
14
|
+
} from '../DatePicker/context';
|
|
15
|
+
import { IconButton } from '../IconButton';
|
|
16
|
+
import { Modal, type ModalProps } from '../Modal';
|
|
17
|
+
import { Portal } from '../Portal';
|
|
18
|
+
import { Text } from '../Text';
|
|
19
|
+
import { TimePickerContext } from './context';
|
|
20
|
+
import { TimePickerClock } from './TimePicker';
|
|
21
|
+
import {
|
|
22
|
+
getTimeInputTypeIcon,
|
|
23
|
+
inputTypes,
|
|
24
|
+
type PossibleInputTypes,
|
|
25
|
+
reverseInputTypes,
|
|
26
|
+
} from './timeUtils';
|
|
27
|
+
import { timePickerModalStyles as styles } from './utils';
|
|
28
|
+
|
|
29
|
+
export type TimePickerModalProps = Omit<ModalProps, 'children' | 'isOpen' | 'onClose'> & {
|
|
30
|
+
children?: ReactNode;
|
|
31
|
+
isOpen?: boolean;
|
|
32
|
+
onClose?: () => void;
|
|
33
|
+
value?: DatePickerValue;
|
|
34
|
+
onChange?: (value: DatePickerValue) => void;
|
|
35
|
+
is24Hour?: boolean;
|
|
36
|
+
inputType?: PossibleInputTypes;
|
|
37
|
+
defaultInputType?: PossibleInputTypes;
|
|
38
|
+
onInputTypeChange?: (next: PossibleInputTypes) => void;
|
|
39
|
+
label?: string;
|
|
40
|
+
uppercase?: boolean;
|
|
41
|
+
cancelLabel?: string;
|
|
42
|
+
confirmLabel?: string;
|
|
43
|
+
keyboardIcon?: string;
|
|
44
|
+
clockIcon?: string;
|
|
45
|
+
/** Override the surface default draft mode. Modal defaults to `true` (staged commit). */
|
|
46
|
+
draft?: boolean;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
type BodyProps = Omit<
|
|
50
|
+
TimePickerModalProps,
|
|
51
|
+
'isOpen' | 'onClose' | 'value' | 'onChange' | 'is24Hour' | 'draft'
|
|
52
|
+
>;
|
|
53
|
+
|
|
54
|
+
function TimePickerModalBody({
|
|
55
|
+
children,
|
|
56
|
+
style,
|
|
57
|
+
label = 'Select time',
|
|
58
|
+
uppercase = false,
|
|
59
|
+
cancelLabel = 'Cancel',
|
|
60
|
+
confirmLabel = 'OK',
|
|
61
|
+
keyboardIcon = 'keyboard-outline',
|
|
62
|
+
clockIcon = 'clock-outline',
|
|
63
|
+
inputType: inputTypeProp,
|
|
64
|
+
defaultInputType = inputTypes.picker,
|
|
65
|
+
onInputTypeChange,
|
|
66
|
+
...rest
|
|
67
|
+
}: BodyProps) {
|
|
68
|
+
const ctx = useDatePickerContext();
|
|
69
|
+
const [inputType, setInputType] = useControlledValue<PossibleInputTypes>({
|
|
70
|
+
value: inputTypeProp,
|
|
71
|
+
defaultValue: defaultInputType,
|
|
72
|
+
onChange: onInputTypeChange,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const tpContextValue = useMemo(() => ({ inputType, setInputType }), [inputType, setInputType]);
|
|
76
|
+
|
|
77
|
+
const modalStyle = useMemo(() => [styles.modalContent, style], [style]);
|
|
78
|
+
|
|
79
|
+
return (
|
|
80
|
+
<Portal>
|
|
81
|
+
<TimePickerContext value={tpContextValue}>
|
|
82
|
+
<Modal {...rest} isOpen={ctx.open} onClose={ctx.cancel} style={modalStyle}>
|
|
83
|
+
<KeyboardAvoidingView
|
|
84
|
+
style={styles.keyboardView}
|
|
85
|
+
behavior={Platform.OS === 'ios' ? 'padding' : undefined}>
|
|
86
|
+
<View style={styles.frame}>
|
|
87
|
+
<View style={styles.labelContainer}>
|
|
88
|
+
<Text style={styles.label}>
|
|
89
|
+
{uppercase ? label.toUpperCase() : label}
|
|
90
|
+
</Text>
|
|
91
|
+
</View>
|
|
92
|
+
<View style={styles.timePickerContainer}>
|
|
93
|
+
{children ?? <TimePickerClock />}
|
|
94
|
+
</View>
|
|
95
|
+
<View style={styles.footer}>
|
|
96
|
+
<IconButton
|
|
97
|
+
name={getTimeInputTypeIcon(inputType, {
|
|
98
|
+
keyboard: keyboardIcon,
|
|
99
|
+
picker: clockIcon,
|
|
100
|
+
})}
|
|
101
|
+
onPress={() => setInputType(reverseInputTypes[inputType])}
|
|
102
|
+
style={styles.inputTypeToggle}
|
|
103
|
+
accessibilityLabel="toggle keyboard"
|
|
104
|
+
/>
|
|
105
|
+
<View style={styles.fill} />
|
|
106
|
+
<DatePickerActions
|
|
107
|
+
cancelLabel={cancelLabel}
|
|
108
|
+
confirmLabel={confirmLabel}
|
|
109
|
+
/>
|
|
110
|
+
</View>
|
|
111
|
+
</View>
|
|
112
|
+
</KeyboardAvoidingView>
|
|
113
|
+
</Modal>
|
|
114
|
+
</TimePickerContext>
|
|
115
|
+
</Portal>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function TimePickerModalLayer({
|
|
120
|
+
base,
|
|
121
|
+
draft: draftProp,
|
|
122
|
+
bodyProps,
|
|
123
|
+
}: {
|
|
124
|
+
base: DatePickerContextType;
|
|
125
|
+
draft: boolean | undefined;
|
|
126
|
+
bodyProps: BodyProps;
|
|
127
|
+
}) {
|
|
128
|
+
const effectiveDraft = draftProp ?? base.providerDraft ?? true;
|
|
129
|
+
const ctx = useMemo(() => withDraftLayer(base, effectiveDraft), [base, effectiveDraft]);
|
|
130
|
+
if (!base.open) return null;
|
|
131
|
+
return (
|
|
132
|
+
<DatePickerContext value={ctx}>
|
|
133
|
+
<TimePickerModalBody {...bodyProps} />
|
|
134
|
+
</DatePickerContext>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function TimePickerModalAdapter({
|
|
139
|
+
draft,
|
|
140
|
+
bodyProps,
|
|
141
|
+
}: {
|
|
142
|
+
draft: boolean | undefined;
|
|
143
|
+
bodyProps: BodyProps;
|
|
144
|
+
}) {
|
|
145
|
+
const base = useDatePickerContext();
|
|
146
|
+
return <TimePickerModalLayer base={base} draft={draft} bodyProps={bodyProps} />;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const TimePickerModalDefault = memo(
|
|
150
|
+
({
|
|
151
|
+
isOpen: isOpenProp,
|
|
152
|
+
onClose: onCloseProp,
|
|
153
|
+
value: valueProp,
|
|
154
|
+
onChange: onChangeProp,
|
|
155
|
+
is24Hour,
|
|
156
|
+
draft: draftProp,
|
|
157
|
+
...rest
|
|
158
|
+
}: TimePickerModalProps) => {
|
|
159
|
+
const outer = useOptionalDatePickerContext();
|
|
160
|
+
|
|
161
|
+
if (outer) {
|
|
162
|
+
return <TimePickerModalLayer base={outer} draft={draftProp} bodyProps={rest} />;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<DatePickerProvider
|
|
167
|
+
mode="time"
|
|
168
|
+
value={valueProp}
|
|
169
|
+
onChange={onChangeProp}
|
|
170
|
+
open={isOpenProp}
|
|
171
|
+
onOpenChange={next => {
|
|
172
|
+
if (!next) onCloseProp?.();
|
|
173
|
+
}}
|
|
174
|
+
is24Hour={is24Hour}>
|
|
175
|
+
<TimePickerModalAdapter draft={draftProp} bodyProps={rest} />
|
|
176
|
+
</DatePickerProvider>
|
|
177
|
+
);
|
|
178
|
+
},
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
export const TimePickerModal = getRegisteredComponentWithFallback(
|
|
182
|
+
'TimePickerModal',
|
|
183
|
+
TimePickerModalDefault,
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
export default TimePickerModal;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
|
|
3
|
+
import { registerPortalContext } from '../Portal';
|
|
4
|
+
import type { PossibleInputTypes } from './timeUtils';
|
|
5
|
+
|
|
6
|
+
export type TimePickerContextType = {
|
|
7
|
+
inputType: PossibleInputTypes;
|
|
8
|
+
setInputType: (next: PossibleInputTypes) => void;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export const TimePickerContext = createContext<TimePickerContextType | null>(null);
|
|
12
|
+
|
|
13
|
+
export function useOptionalTimePickerContext(): TimePickerContextType | null {
|
|
14
|
+
return useContext(TimePickerContext);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
registerPortalContext(TimePickerContext);
|
|
@@ -1,9 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
1
|
+
import { DatePickerProvider, DatePickerTrigger } from '../DatePicker';
|
|
2
|
+
import { TimePickerClock } from './TimePicker';
|
|
3
|
+
import { TimePickerModal } from './TimePickerModal';
|
|
3
4
|
|
|
4
|
-
export const TimePicker =
|
|
5
|
+
export const TimePicker = {
|
|
6
|
+
Provider: DatePickerProvider,
|
|
7
|
+
Trigger: DatePickerTrigger,
|
|
8
|
+
Clock: TimePickerClock,
|
|
9
|
+
Modal: TimePickerModal,
|
|
10
|
+
};
|
|
5
11
|
|
|
12
|
+
export type { TimePickerContextType } from './context';
|
|
13
|
+
export { TimePickerContext, useOptionalTimePickerContext } from './context';
|
|
6
14
|
export type { Props as TimePickerProps } from './TimePicker';
|
|
15
|
+
export { TimePickerClock } from './TimePicker';
|
|
16
|
+
export type { TimePickerModalProps } from './TimePickerModal';
|
|
17
|
+
export { TimePickerModal } from './TimePickerModal';
|
|
7
18
|
export {
|
|
8
19
|
timePickerAmPmSwitcherStyles,
|
|
9
20
|
timePickerClockHoursStyles,
|
|
@@ -11,5 +22,6 @@ export {
|
|
|
11
22
|
timePickerClockStyles,
|
|
12
23
|
timePickerInputsStyles,
|
|
13
24
|
timePickerInputStyles,
|
|
25
|
+
timePickerModalStyles,
|
|
14
26
|
timePickerStyles,
|
|
15
27
|
} from './utils';
|
|
@@ -218,6 +218,52 @@ const timePickerClockMinutesStylesDefault = StyleSheet.create(theme => ({
|
|
|
218
218
|
textWhite: { color: '#fff' },
|
|
219
219
|
}));
|
|
220
220
|
|
|
221
|
+
const timePickerModalStylesDefault = StyleSheet.create(theme => ({
|
|
222
|
+
keyboardView: {
|
|
223
|
+
justifyContent: 'center',
|
|
224
|
+
alignItems: 'center',
|
|
225
|
+
flex: 1,
|
|
226
|
+
},
|
|
227
|
+
modalContent: {
|
|
228
|
+
minWidth: 287,
|
|
229
|
+
width: undefined,
|
|
230
|
+
maxWidth: undefined,
|
|
231
|
+
flex: undefined,
|
|
232
|
+
borderRadius: theme.shapes.corner.extraLarge,
|
|
233
|
+
overflow: 'hidden',
|
|
234
|
+
},
|
|
235
|
+
frame: {
|
|
236
|
+
backgroundColor: theme.colors.surface,
|
|
237
|
+
},
|
|
238
|
+
labelContainer: {
|
|
239
|
+
minHeight: 56,
|
|
240
|
+
justifyContent: 'flex-end',
|
|
241
|
+
paddingLeft: theme.spacings['6'],
|
|
242
|
+
paddingRight: theme.spacings['6'],
|
|
243
|
+
paddingTop: theme.spacings['6'],
|
|
244
|
+
paddingBottom: theme.spacings['2'],
|
|
245
|
+
alignSelf: 'flex-start',
|
|
246
|
+
},
|
|
247
|
+
label: {
|
|
248
|
+
letterSpacing: 1,
|
|
249
|
+
fontSize: theme.typescale.labelLarge.fontSize,
|
|
250
|
+
color: theme.colors.onSurface,
|
|
251
|
+
fontWeight: theme.typescale.labelLarge.fontWeight,
|
|
252
|
+
},
|
|
253
|
+
timePickerContainer: {
|
|
254
|
+
padding: theme.spacings['6'],
|
|
255
|
+
paddingTop: theme.spacings['2'],
|
|
256
|
+
},
|
|
257
|
+
footer: {
|
|
258
|
+
flexDirection: 'row',
|
|
259
|
+
alignItems: 'center',
|
|
260
|
+
padding: theme.spacings['2'],
|
|
261
|
+
width: '100%',
|
|
262
|
+
},
|
|
263
|
+
inputTypeToggle: { margin: theme.spacings['1'] },
|
|
264
|
+
fill: { flex: 1 },
|
|
265
|
+
}));
|
|
266
|
+
|
|
221
267
|
const timePickerAmPmSwitcherStylesDefault = StyleSheet.create(theme => ({
|
|
222
268
|
container: {
|
|
223
269
|
width: 50,
|
|
@@ -291,3 +337,7 @@ export const timePickerAmPmSwitcherStyles = getRegisteredComponentStylesWithFall
|
|
|
291
337
|
'TimePicker_AmPmSwitcher',
|
|
292
338
|
timePickerAmPmSwitcherStylesDefault,
|
|
293
339
|
);
|
|
340
|
+
export const timePickerModalStyles = getRegisteredComponentStylesWithFallback(
|
|
341
|
+
'TimePickerModal',
|
|
342
|
+
timePickerModalStylesDefault,
|
|
343
|
+
);
|
package/hooks/useActionState.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RefObject, useRef } from 'react';
|
|
1
|
+
import { type RefObject, useCallback, useRef } from 'react';
|
|
2
2
|
|
|
3
3
|
import { useActive } from './useActive';
|
|
4
4
|
import { useFocus } from './useFocus';
|
|
@@ -14,16 +14,27 @@ export type UseActionStateProps = {
|
|
|
14
14
|
export const useActionState = (
|
|
15
15
|
props: UseActionStateProps & { ref?: RefObject<any> | React.ForwardedRef<any> } = {},
|
|
16
16
|
) => {
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const internalRef = useRef(null);
|
|
18
|
+
const externalRef = props.ref;
|
|
19
|
+
|
|
20
|
+
const actionsRef = useCallback(
|
|
21
|
+
(node: any) => {
|
|
22
|
+
internalRef.current = node;
|
|
23
|
+
if (typeof externalRef === 'function') {
|
|
24
|
+
externalRef(node);
|
|
25
|
+
} else if (externalRef) {
|
|
26
|
+
(externalRef as RefObject<any>).current = node;
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
[externalRef],
|
|
30
|
+
) as unknown as RefObject<any>;
|
|
31
|
+
|
|
21
32
|
const hovered =
|
|
22
|
-
useHover(
|
|
33
|
+
useHover(internalRef, props.actionsToListen?.includes('hover')) || !!props.hovered;
|
|
23
34
|
const pressed =
|
|
24
|
-
useActive(
|
|
35
|
+
useActive(internalRef, props.actionsToListen?.includes('press')) || !!props.pressed;
|
|
25
36
|
const focused =
|
|
26
|
-
useFocus(
|
|
37
|
+
useFocus(internalRef, props.actionsToListen?.includes('focus')) || !!props.focused;
|
|
27
38
|
|
|
28
39
|
return {
|
|
29
40
|
actionsRef,
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-molecules",
|
|
3
|
-
"version": "0.5.0-beta.
|
|
3
|
+
"version": "0.5.0-beta.18",
|
|
4
4
|
"author": "Thet Aung <thetaung.dev@gmail.com>",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "index.ts",
|
|
7
|
+
"sideEffects": [
|
|
8
|
+
"components/DatePicker/context.tsx",
|
|
9
|
+
"components/Select/context.tsx",
|
|
10
|
+
"components/TimePicker/context.tsx"
|
|
11
|
+
],
|
|
7
12
|
"files": [
|
|
8
13
|
"components",
|
|
9
14
|
"context-bridge",
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { memo } from 'react';
|
|
2
|
-
|
|
3
|
-
import DatePickerInlineBase from '../DatePickerInline/DatePickerInlineBase';
|
|
4
|
-
import { Popover } from '../Popover';
|
|
5
|
-
import DatePickerDockedHeader from './DatePickerDockedHeader';
|
|
6
|
-
import type { DatePickerDockedProps } from './types';
|
|
7
|
-
import { datePickerDockedMonthStyles } from './utils';
|
|
8
|
-
|
|
9
|
-
const DatePickerDocked = (props: DatePickerDockedProps) => {
|
|
10
|
-
const { triggerRef, isOpen, onToggle, onClose } = props;
|
|
11
|
-
|
|
12
|
-
return (
|
|
13
|
-
<Popover
|
|
14
|
-
triggerRef={triggerRef}
|
|
15
|
-
isOpen={isOpen}
|
|
16
|
-
onClose={onClose}
|
|
17
|
-
{...props.popoverContentProps}>
|
|
18
|
-
<DatePickerInlineBase
|
|
19
|
-
{...props}
|
|
20
|
-
// TODO - fix ts issues
|
|
21
|
-
// @ts-ignore
|
|
22
|
-
HeaderComponent={DatePickerDockedHeader}
|
|
23
|
-
onToggle={onToggle}
|
|
24
|
-
monthStyle={datePickerDockedMonthStyles}
|
|
25
|
-
/>
|
|
26
|
-
</Popover>
|
|
27
|
-
);
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
export default memo(DatePickerDocked);
|