react-native-molecules 0.5.0-beta.2 → 0.5.0-beta.21
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.ios.tsx +10 -14
- package/components/Checkbox/CheckboxBase.tsx +14 -121
- 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 +111 -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 +245 -0
- package/components/DatePicker/DatePickerPopover.tsx +79 -0
- package/components/DatePicker/DatePickerProvider.tsx +158 -0
- package/components/DatePicker/DatePickerTrigger.tsx +23 -0
- package/components/DatePicker/context.tsx +83 -0
- package/components/DatePicker/index.tsx +45 -0
- package/components/DatePicker/utils.ts +293 -0
- package/components/DatePickerInline/DatePickerContext.tsx +1 -0
- package/components/DatePickerInline/DatePickerDockedHeader.tsx +117 -0
- package/components/DatePickerInline/DatePickerInline.tsx +16 -15
- package/components/DatePickerInline/DatePickerInlineBase.tsx +8 -2
- package/components/DatePickerInline/DatePickerInlineHeader.tsx +8 -4
- package/components/DatePickerInline/Day.tsx +25 -1
- package/components/DatePickerInline/DayNames.tsx +13 -10
- package/components/DatePickerInline/DayRange.tsx +2 -4
- package/components/DatePickerInline/HeaderItem.tsx +42 -27
- package/components/DatePickerInline/Month.tsx +48 -67
- package/components/DatePickerInline/MonthPicker.tsx +38 -44
- 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 +6 -2
- 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
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
|
+
import { View } from 'react-native';
|
|
3
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
getRegisteredComponentStylesWithFallback,
|
|
7
|
+
getRegisteredComponentWithFallback,
|
|
8
|
+
} from '../../core';
|
|
9
|
+
import { Button } from '../Button';
|
|
10
|
+
import { useDatePickerContext } from './context';
|
|
11
|
+
|
|
12
|
+
export type DatePickerActionsProps = {
|
|
13
|
+
cancelLabel?: string;
|
|
14
|
+
confirmLabel?: string;
|
|
15
|
+
onCancel?: () => void;
|
|
16
|
+
onConfirm?: () => void;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function DatePickerActionsDefault({
|
|
20
|
+
cancelLabel = 'Cancel',
|
|
21
|
+
confirmLabel = 'OK',
|
|
22
|
+
onCancel,
|
|
23
|
+
onConfirm,
|
|
24
|
+
}: DatePickerActionsProps) {
|
|
25
|
+
const { draft, commit, cancel } = useDatePickerContext();
|
|
26
|
+
|
|
27
|
+
const handleCancel = useCallback(() => {
|
|
28
|
+
onCancel?.();
|
|
29
|
+
cancel();
|
|
30
|
+
}, [onCancel, cancel]);
|
|
31
|
+
|
|
32
|
+
const handleConfirm = useCallback(() => {
|
|
33
|
+
onConfirm?.();
|
|
34
|
+
commit();
|
|
35
|
+
}, [onConfirm, commit]);
|
|
36
|
+
|
|
37
|
+
if (!draft) return null;
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<View style={datePickerActionsStyles.footer}>
|
|
41
|
+
<Button variant="text" onPress={handleCancel}>
|
|
42
|
+
<Button.Text>{cancelLabel}</Button.Text>
|
|
43
|
+
</Button>
|
|
44
|
+
<Button variant="text" onPress={handleConfirm}>
|
|
45
|
+
<Button.Text>{confirmLabel}</Button.Text>
|
|
46
|
+
</Button>
|
|
47
|
+
</View>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default DatePickerActionsDefault;
|
|
52
|
+
|
|
53
|
+
export const DatePickerActions = getRegisteredComponentWithFallback(
|
|
54
|
+
'DatePickerActions',
|
|
55
|
+
DatePickerActionsDefault,
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
const datePickerActionsStylesDefault = StyleSheet.create(theme => ({
|
|
59
|
+
footer: {
|
|
60
|
+
flexDirection: 'row',
|
|
61
|
+
alignItems: 'center',
|
|
62
|
+
justifyContent: 'flex-end',
|
|
63
|
+
columnGap: theme.spacings['2'],
|
|
64
|
+
paddingHorizontal: theme.spacings['4'],
|
|
65
|
+
paddingTop: theme.spacings['0'],
|
|
66
|
+
paddingBottom: theme.spacings['2'],
|
|
67
|
+
},
|
|
68
|
+
}));
|
|
69
|
+
|
|
70
|
+
const datePickerActionsStyles = getRegisteredComponentStylesWithFallback(
|
|
71
|
+
'DatePickerActions',
|
|
72
|
+
datePickerActionsStylesDefault,
|
|
73
|
+
);
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { memo, useCallback, useMemo, useState } from 'react';
|
|
3
|
+
import {
|
|
4
|
+
Platform,
|
|
5
|
+
StatusBar,
|
|
6
|
+
type StatusBarStyle,
|
|
7
|
+
StyleSheet,
|
|
8
|
+
useWindowDimensions,
|
|
9
|
+
View,
|
|
10
|
+
} from 'react-native';
|
|
11
|
+
|
|
12
|
+
import { getRegisteredComponentWithFallback } from '../../core';
|
|
13
|
+
import { DateField } from '../DateField';
|
|
14
|
+
import { IconButton } from '../IconButton';
|
|
15
|
+
import { Modal, type ModalProps } from '../Modal';
|
|
16
|
+
import { Portal } from '../Portal';
|
|
17
|
+
import { Text } from '../Text';
|
|
18
|
+
import { TextInput } from '../TextInput';
|
|
19
|
+
import type { DatePickerContextType, DatePickerLocale, DatePickerValue } from './context';
|
|
20
|
+
import {
|
|
21
|
+
DatePickerContext,
|
|
22
|
+
useDatePickerContext,
|
|
23
|
+
useOptionalDatePickerContext,
|
|
24
|
+
withDraftLayer,
|
|
25
|
+
} from './context';
|
|
26
|
+
import { DateCalendar } from './DateCalendar';
|
|
27
|
+
import { DatePickerActions } from './DatePickerActions';
|
|
28
|
+
import { DatePickerProvider } from './DatePickerProvider';
|
|
29
|
+
import { datePickerModalStyles } from './utils';
|
|
30
|
+
|
|
31
|
+
export type DatePickerModalProps = Omit<ModalProps, 'children' | 'isOpen' | 'onClose'> & {
|
|
32
|
+
children?: ReactNode;
|
|
33
|
+
isOpen?: boolean;
|
|
34
|
+
onClose?: () => void;
|
|
35
|
+
value?: DatePickerValue;
|
|
36
|
+
onChange?: (value: DatePickerValue) => void;
|
|
37
|
+
label?: string;
|
|
38
|
+
disableStatusBar?: boolean;
|
|
39
|
+
disableStatusBarPadding?: boolean;
|
|
40
|
+
confirmLabel?: string;
|
|
41
|
+
cancelLabel?: string;
|
|
42
|
+
editIcon?: string;
|
|
43
|
+
calendarIcon?: string;
|
|
44
|
+
emptySummaryLabel?: string;
|
|
45
|
+
editingSummaryLabel?: string;
|
|
46
|
+
dateInputLabel?: string;
|
|
47
|
+
showCalendarAccessibilityLabel?: string;
|
|
48
|
+
enterDateManuallyAccessibilityLabel?: string;
|
|
49
|
+
locale?: DatePickerLocale;
|
|
50
|
+
headerLayout?: 'inline' | 'docked';
|
|
51
|
+
/** Override the surface default draft mode. Modal defaults to `true` (staged commit). */
|
|
52
|
+
draft?: boolean;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
type BodyProps = Omit<DatePickerModalProps, 'isOpen' | 'onClose' | 'value' | 'onChange' | 'draft'>;
|
|
56
|
+
|
|
57
|
+
function DatePickerModalBody({
|
|
58
|
+
children,
|
|
59
|
+
style,
|
|
60
|
+
label = 'Select date',
|
|
61
|
+
disableStatusBar,
|
|
62
|
+
disableStatusBarPadding,
|
|
63
|
+
confirmLabel = 'OK',
|
|
64
|
+
cancelLabel = 'Cancel',
|
|
65
|
+
editIcon = 'pencil',
|
|
66
|
+
calendarIcon = 'calendar',
|
|
67
|
+
emptySummaryLabel = 'Select date',
|
|
68
|
+
editingSummaryLabel = 'Enter dates',
|
|
69
|
+
dateInputLabel = 'Date',
|
|
70
|
+
showCalendarAccessibilityLabel = 'Show calendar',
|
|
71
|
+
enterDateManuallyAccessibilityLabel = 'Enter date manually',
|
|
72
|
+
headerLayout,
|
|
73
|
+
...rest
|
|
74
|
+
}: BodyProps) {
|
|
75
|
+
const ctx = useDatePickerContext();
|
|
76
|
+
const dimensions = useWindowDimensions();
|
|
77
|
+
const [editing, setEditing] = useState(false);
|
|
78
|
+
|
|
79
|
+
const { containerStyle, headerStyle, barStyle, modalStyle } = useMemo(() => {
|
|
80
|
+
const isHeaderBackgroundLight = true;
|
|
81
|
+
return {
|
|
82
|
+
containerStyle: [StyleSheet.absoluteFill, styles.container],
|
|
83
|
+
headerStyle: [
|
|
84
|
+
datePickerModalStyles.headlineContainer,
|
|
85
|
+
{
|
|
86
|
+
paddingTop:
|
|
87
|
+
disableStatusBarPadding || Platform.OS !== 'android'
|
|
88
|
+
? datePickerModalStyles.headlineContainer.paddingTop
|
|
89
|
+
: (datePickerModalStyles.headlineContainer.paddingTop as number) +
|
|
90
|
+
(StatusBar.currentHeight || 0),
|
|
91
|
+
},
|
|
92
|
+
],
|
|
93
|
+
barStyle: (isHeaderBackgroundLight
|
|
94
|
+
? 'dark-content'
|
|
95
|
+
: 'light-content') as StatusBarStyle,
|
|
96
|
+
modalStyle: [
|
|
97
|
+
datePickerModalStyles.content,
|
|
98
|
+
dimensions.width > 650 ? { maxHeight: 600 } : { borderRadius: 0 },
|
|
99
|
+
style,
|
|
100
|
+
],
|
|
101
|
+
};
|
|
102
|
+
}, [dimensions.width, disableStatusBarPadding, style]);
|
|
103
|
+
|
|
104
|
+
const draft =
|
|
105
|
+
ctx.draftValue && typeof ctx.draftValue === 'object' && 'start' in ctx.draftValue
|
|
106
|
+
? null
|
|
107
|
+
: ctx.draftValue;
|
|
108
|
+
|
|
109
|
+
const summaryLabel = useMemo(() => {
|
|
110
|
+
if (editing) return editingSummaryLabel;
|
|
111
|
+
if (!draft) return emptySummaryLabel;
|
|
112
|
+
return new Intl.DateTimeFormat(ctx.locale, {
|
|
113
|
+
weekday: 'short',
|
|
114
|
+
month: 'short',
|
|
115
|
+
day: 'numeric',
|
|
116
|
+
}).format(draft);
|
|
117
|
+
}, [ctx.locale, draft, editing, editingSummaryLabel, emptySummaryLabel]);
|
|
118
|
+
|
|
119
|
+
const body = editing ? (
|
|
120
|
+
<View style={datePickerModalStyles.inputContainer}>
|
|
121
|
+
<DateField autoFocus>
|
|
122
|
+
<TextInput.Label>{dateInputLabel}</TextInput.Label>
|
|
123
|
+
</DateField>
|
|
124
|
+
</View>
|
|
125
|
+
) : (
|
|
126
|
+
children ?? <DateCalendar headerLayout={headerLayout} showOutsideDays={false} />
|
|
127
|
+
);
|
|
128
|
+
|
|
129
|
+
return (
|
|
130
|
+
<View style={containerStyle} pointerEvents="box-none">
|
|
131
|
+
<Portal>
|
|
132
|
+
<Modal
|
|
133
|
+
{...rest}
|
|
134
|
+
isOpen={ctx.open}
|
|
135
|
+
onClose={ctx.cancel}
|
|
136
|
+
style={modalStyle}
|
|
137
|
+
elevation={0}>
|
|
138
|
+
<>
|
|
139
|
+
{disableStatusBar ? null : (
|
|
140
|
+
<StatusBar translucent={true} barStyle={barStyle} />
|
|
141
|
+
)}
|
|
142
|
+
<View style={datePickerModalStyles.frame}>
|
|
143
|
+
<View style={headerStyle}>
|
|
144
|
+
<View style={datePickerModalStyles.headerCopy}>
|
|
145
|
+
<Text style={datePickerModalStyles.headline}>{label}</Text>
|
|
146
|
+
<Text
|
|
147
|
+
style={datePickerModalStyles.supporting}
|
|
148
|
+
numberOfLines={1}
|
|
149
|
+
adjustsFontSizeToFit={true}>
|
|
150
|
+
{summaryLabel}
|
|
151
|
+
</Text>
|
|
152
|
+
</View>
|
|
153
|
+
<IconButton
|
|
154
|
+
name={editing ? calendarIcon : editIcon}
|
|
155
|
+
accessibilityLabel={
|
|
156
|
+
editing
|
|
157
|
+
? showCalendarAccessibilityLabel
|
|
158
|
+
: enterDateManuallyAccessibilityLabel
|
|
159
|
+
}
|
|
160
|
+
onPress={() => setEditing(prev => !prev)}
|
|
161
|
+
style={datePickerModalStyles.modeToggle}
|
|
162
|
+
/>
|
|
163
|
+
</View>
|
|
164
|
+
<View style={datePickerModalStyles.body}>{body}</View>
|
|
165
|
+
<DatePickerActions
|
|
166
|
+
cancelLabel={cancelLabel}
|
|
167
|
+
confirmLabel={confirmLabel}
|
|
168
|
+
/>
|
|
169
|
+
</View>
|
|
170
|
+
</>
|
|
171
|
+
</Modal>
|
|
172
|
+
</Portal>
|
|
173
|
+
</View>
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function DatePickerModalLayer({
|
|
178
|
+
base,
|
|
179
|
+
draft: draftProp,
|
|
180
|
+
...rest
|
|
181
|
+
}: BodyProps & {
|
|
182
|
+
base: DatePickerContextType;
|
|
183
|
+
draft: boolean | undefined;
|
|
184
|
+
}) {
|
|
185
|
+
const effectiveDraft = draftProp ?? base.providerDraft ?? true;
|
|
186
|
+
const ctx = useMemo(() => withDraftLayer(base, effectiveDraft), [base, effectiveDraft]);
|
|
187
|
+
if (!base.open) return null;
|
|
188
|
+
return (
|
|
189
|
+
<DatePickerContext value={ctx}>
|
|
190
|
+
<DatePickerModalBody {...rest} />
|
|
191
|
+
</DatePickerContext>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function DatePickerModalAdapter({ draft, ...rest }: BodyProps & { draft: boolean | undefined }) {
|
|
196
|
+
const base = useDatePickerContext();
|
|
197
|
+
return <DatePickerModalLayer base={base} draft={draft} {...rest} />;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function DatePickerModalInner({
|
|
201
|
+
isOpen: isOpenProp,
|
|
202
|
+
onClose: onCloseProp,
|
|
203
|
+
value: valueProp,
|
|
204
|
+
onChange: onChangeProp,
|
|
205
|
+
locale,
|
|
206
|
+
draft: draftProp,
|
|
207
|
+
...rest
|
|
208
|
+
}: DatePickerModalProps) {
|
|
209
|
+
const outer = useOptionalDatePickerContext();
|
|
210
|
+
|
|
211
|
+
const onOpenChange = useCallback(
|
|
212
|
+
(next: boolean) => {
|
|
213
|
+
if (!next) onCloseProp?.();
|
|
214
|
+
},
|
|
215
|
+
[onCloseProp],
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
if (outer) {
|
|
219
|
+
return <DatePickerModalLayer base={outer} draft={draftProp} {...rest} />;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<DatePickerProvider
|
|
224
|
+
value={valueProp}
|
|
225
|
+
onChange={onChangeProp}
|
|
226
|
+
open={isOpenProp}
|
|
227
|
+
locale={locale}
|
|
228
|
+
onOpenChange={onOpenChange}>
|
|
229
|
+
<DatePickerModalAdapter draft={draftProp} {...rest} />
|
|
230
|
+
</DatePickerProvider>
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const styles = StyleSheet.create({
|
|
235
|
+
container: { backgroundColor: 'transparent' },
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
const DatePickerModalDefault = memo(DatePickerModalInner);
|
|
239
|
+
|
|
240
|
+
export default DatePickerModalDefault;
|
|
241
|
+
|
|
242
|
+
export const DatePickerModal = getRegisteredComponentWithFallback(
|
|
243
|
+
'DatePickerModal',
|
|
244
|
+
DatePickerModalDefault,
|
|
245
|
+
);
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { ReactNode, RefObject } from 'react';
|
|
2
|
+
import { memo, useMemo } from 'react';
|
|
3
|
+
import { View } from 'react-native';
|
|
4
|
+
import { StyleSheet } from 'react-native-unistyles';
|
|
5
|
+
|
|
6
|
+
import { getRegisteredComponentWithFallback } from '../../core';
|
|
7
|
+
import { Popover, type PopoverProps } from '../Popover';
|
|
8
|
+
import { DatePickerContext, useOptionalDatePickerContext, withDraftLayer } from './context';
|
|
9
|
+
import { DatePickerActions } from './DatePickerActions';
|
|
10
|
+
|
|
11
|
+
export type DatePickerPopoverProps = Omit<
|
|
12
|
+
PopoverProps,
|
|
13
|
+
'children' | 'triggerRef' | 'isOpen' | 'onClose'
|
|
14
|
+
> & {
|
|
15
|
+
children: ReactNode;
|
|
16
|
+
triggerRef?: RefObject<any>;
|
|
17
|
+
isOpen?: boolean;
|
|
18
|
+
onClose?: () => void;
|
|
19
|
+
/** Override the surface default draft mode. Popover defaults to `false` (auto-save). */
|
|
20
|
+
draft?: boolean;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
function DatePickerPopoverInner({
|
|
24
|
+
children,
|
|
25
|
+
triggerRef: triggerRefProp,
|
|
26
|
+
isOpen: isOpenProp,
|
|
27
|
+
onClose: onCloseProp,
|
|
28
|
+
draft: draftProp,
|
|
29
|
+
...rest
|
|
30
|
+
}: DatePickerPopoverProps) {
|
|
31
|
+
const base = useOptionalDatePickerContext();
|
|
32
|
+
|
|
33
|
+
const effectiveDraft = draftProp ?? base?.providerDraft ?? false;
|
|
34
|
+
const ctx = useMemo(
|
|
35
|
+
() => (base ? withDraftLayer(base, effectiveDraft) : null),
|
|
36
|
+
[base, effectiveDraft],
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const triggerRef = triggerRefProp ?? ctx?.triggerRef;
|
|
40
|
+
const isOpen = isOpenProp ?? ctx?.open ?? false;
|
|
41
|
+
const onClose = onCloseProp ?? (() => ctx?.setOpen(false));
|
|
42
|
+
|
|
43
|
+
if (!triggerRef || !ctx) return null;
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<DatePickerContext value={ctx}>
|
|
47
|
+
<Popover
|
|
48
|
+
align="end"
|
|
49
|
+
position="bottom"
|
|
50
|
+
offset={20}
|
|
51
|
+
{...rest}
|
|
52
|
+
style={[styles.popover, rest.style]}
|
|
53
|
+
triggerRef={triggerRef}
|
|
54
|
+
isOpen={isOpen}
|
|
55
|
+
onClose={onClose}>
|
|
56
|
+
<View>
|
|
57
|
+
{children}
|
|
58
|
+
<DatePickerActions />
|
|
59
|
+
</View>
|
|
60
|
+
</Popover>
|
|
61
|
+
</DatePickerContext>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const styles = StyleSheet.create(theme => ({
|
|
66
|
+
popover: {
|
|
67
|
+
borderRadius: theme.shapes.corner.large,
|
|
68
|
+
overflow: 'hidden',
|
|
69
|
+
},
|
|
70
|
+
}));
|
|
71
|
+
|
|
72
|
+
const DatePickerPopoverDefault = memo(DatePickerPopoverInner);
|
|
73
|
+
|
|
74
|
+
export default DatePickerPopoverDefault;
|
|
75
|
+
|
|
76
|
+
export const DatePickerPopover = getRegisteredComponentWithFallback(
|
|
77
|
+
'DatePickerPopover',
|
|
78
|
+
DatePickerPopoverDefault,
|
|
79
|
+
);
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { memo, useCallback, useMemo, useRef, useState } from 'react';
|
|
3
|
+
|
|
4
|
+
import { getRegisteredComponentWithFallback } from '../../core';
|
|
5
|
+
import { useControlledValue } from '../../hooks';
|
|
6
|
+
import type { ValidRangeType } from '../DatePickerInline';
|
|
7
|
+
import type {
|
|
8
|
+
DatePickerContextType,
|
|
9
|
+
DatePickerLocale,
|
|
10
|
+
DatePickerMode,
|
|
11
|
+
DatePickerValue,
|
|
12
|
+
RangeValue,
|
|
13
|
+
} from './context';
|
|
14
|
+
import { DatePickerContext, withDraftLayer } from './context';
|
|
15
|
+
|
|
16
|
+
const DATE_FORMAT_BY_MODE: Record<DatePickerMode, string> = {
|
|
17
|
+
date: 'dd/MM/yyyy',
|
|
18
|
+
time: 'HH:mm',
|
|
19
|
+
datetime: 'dd/MM/yyyy HH:mm',
|
|
20
|
+
range: 'dd/MM/yyyy',
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const emptyRange: RangeValue = { start: null, end: null };
|
|
24
|
+
|
|
25
|
+
const nullValueFor = (mode: DatePickerMode): DatePickerValue =>
|
|
26
|
+
mode === 'range' ? emptyRange : null;
|
|
27
|
+
|
|
28
|
+
export type DatePickerProviderProps = {
|
|
29
|
+
mode?: DatePickerMode;
|
|
30
|
+
value?: DatePickerValue;
|
|
31
|
+
defaultValue?: DatePickerValue;
|
|
32
|
+
onChange?: (value: DatePickerValue) => void;
|
|
33
|
+
|
|
34
|
+
draft?: boolean;
|
|
35
|
+
open?: boolean;
|
|
36
|
+
defaultOpen?: boolean;
|
|
37
|
+
onOpenChange?: (open: boolean) => void;
|
|
38
|
+
|
|
39
|
+
locale?: DatePickerLocale;
|
|
40
|
+
validRange?: ValidRangeType;
|
|
41
|
+
is24Hour?: boolean;
|
|
42
|
+
dateFormat?: string;
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
|
|
45
|
+
children: ReactNode;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
function DatePickerProviderInner({
|
|
49
|
+
mode = 'date',
|
|
50
|
+
value: valueProp,
|
|
51
|
+
defaultValue,
|
|
52
|
+
onChange,
|
|
53
|
+
draft: draftProp,
|
|
54
|
+
open: openProp,
|
|
55
|
+
defaultOpen = false,
|
|
56
|
+
onOpenChange,
|
|
57
|
+
locale,
|
|
58
|
+
validRange,
|
|
59
|
+
is24Hour = false,
|
|
60
|
+
dateFormat,
|
|
61
|
+
disabled,
|
|
62
|
+
children,
|
|
63
|
+
}: DatePickerProviderProps) {
|
|
64
|
+
const triggerRef = useRef<any>(null);
|
|
65
|
+
|
|
66
|
+
const [value, setCommittedValue] = useControlledValue<DatePickerValue>({
|
|
67
|
+
value: valueProp,
|
|
68
|
+
defaultValue: defaultValue ?? nullValueFor(mode),
|
|
69
|
+
onChange,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
const [open, setOpen] = useControlledValue<boolean>({
|
|
73
|
+
value: openProp,
|
|
74
|
+
defaultValue: defaultOpen,
|
|
75
|
+
onChange: onOpenChange,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const [draftBuffer, setDraftBuffer] = useState<DatePickerValue>(value);
|
|
79
|
+
|
|
80
|
+
const prevOpenRef = useRef(open);
|
|
81
|
+
const prevValueRef = useRef(value);
|
|
82
|
+
|
|
83
|
+
if (open && !prevOpenRef.current && draftBuffer !== value) {
|
|
84
|
+
setDraftBuffer(value);
|
|
85
|
+
}
|
|
86
|
+
if (prevValueRef.current !== value && draftBuffer !== value) {
|
|
87
|
+
setDraftBuffer(value);
|
|
88
|
+
}
|
|
89
|
+
prevOpenRef.current = open;
|
|
90
|
+
prevValueRef.current = value;
|
|
91
|
+
|
|
92
|
+
const commitValue = useCallback(
|
|
93
|
+
(next: DatePickerValue) => {
|
|
94
|
+
setCommittedValue(next);
|
|
95
|
+
setDraftBuffer(next);
|
|
96
|
+
},
|
|
97
|
+
[setCommittedValue],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const resolvedFormat = dateFormat ?? DATE_FORMAT_BY_MODE[mode];
|
|
101
|
+
const defaultDraft = draftProp ?? true;
|
|
102
|
+
|
|
103
|
+
const ctx = useMemo<DatePickerContextType>(() => {
|
|
104
|
+
const base: DatePickerContextType = {
|
|
105
|
+
mode,
|
|
106
|
+
value,
|
|
107
|
+
draftBuffer,
|
|
108
|
+
setCommittedValue,
|
|
109
|
+
setDraftBuffer,
|
|
110
|
+
providerDraft: draftProp,
|
|
111
|
+
|
|
112
|
+
// Placeholders — withDraftLayer rebuilds these.
|
|
113
|
+
draft: defaultDraft,
|
|
114
|
+
draftValue: value,
|
|
115
|
+
setValue: () => {},
|
|
116
|
+
commit: () => {},
|
|
117
|
+
cancel: () => {},
|
|
118
|
+
|
|
119
|
+
commitValue,
|
|
120
|
+
|
|
121
|
+
open,
|
|
122
|
+
setOpen,
|
|
123
|
+
triggerRef,
|
|
124
|
+
locale,
|
|
125
|
+
validRange,
|
|
126
|
+
is24Hour,
|
|
127
|
+
dateFormat: resolvedFormat,
|
|
128
|
+
disabled,
|
|
129
|
+
};
|
|
130
|
+
return withDraftLayer(base, defaultDraft);
|
|
131
|
+
}, [
|
|
132
|
+
mode,
|
|
133
|
+
value,
|
|
134
|
+
draftBuffer,
|
|
135
|
+
setCommittedValue,
|
|
136
|
+
draftProp,
|
|
137
|
+
defaultDraft,
|
|
138
|
+
commitValue,
|
|
139
|
+
open,
|
|
140
|
+
setOpen,
|
|
141
|
+
locale,
|
|
142
|
+
validRange,
|
|
143
|
+
is24Hour,
|
|
144
|
+
resolvedFormat,
|
|
145
|
+
disabled,
|
|
146
|
+
]);
|
|
147
|
+
|
|
148
|
+
return <DatePickerContext value={ctx}>{children}</DatePickerContext>;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const DatePickerProviderDefault = memo(DatePickerProviderInner);
|
|
152
|
+
|
|
153
|
+
export default DatePickerProviderDefault;
|
|
154
|
+
|
|
155
|
+
export const DatePickerProvider = getRegisteredComponentWithFallback(
|
|
156
|
+
'DatePickerProvider',
|
|
157
|
+
DatePickerProviderDefault,
|
|
158
|
+
);
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type { ReactElement } from 'react';
|
|
2
|
+
import { memo, useCallback } from 'react';
|
|
3
|
+
|
|
4
|
+
import { getRegisteredComponentWithFallback } from '../../core';
|
|
5
|
+
import { Slot } from '../Slot';
|
|
6
|
+
import { useDatePickerContext } from './context';
|
|
7
|
+
|
|
8
|
+
const DatePickerTriggerDefault = memo(({ children }: { children: ReactElement }) => {
|
|
9
|
+
const { open, setOpen, disabled, triggerRef } = useDatePickerContext();
|
|
10
|
+
|
|
11
|
+
const onPress = useCallback(() => setOpen(!open), [open, setOpen]);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<Slot ref={triggerRef} onPress={onPress} disabled={disabled}>
|
|
15
|
+
{children}
|
|
16
|
+
</Slot>
|
|
17
|
+
);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
export const DatePickerTrigger = getRegisteredComponentWithFallback(
|
|
21
|
+
'DatePickerTrigger',
|
|
22
|
+
DatePickerTriggerDefault,
|
|
23
|
+
);
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { RefObject } from 'react';
|
|
2
|
+
import { createContext, useContext } from 'react';
|
|
3
|
+
|
|
4
|
+
import type { ValidRangeType } from '../DatePickerInline';
|
|
5
|
+
import { registerPortalContext } from '../Portal';
|
|
6
|
+
|
|
7
|
+
export type DatePickerMode = 'date' | 'time' | 'datetime' | 'range';
|
|
8
|
+
export type DatePickerLocale = Intl.LocalesArgument;
|
|
9
|
+
|
|
10
|
+
export type DateValue = Date | null;
|
|
11
|
+
export type RangeValue = { start: Date | null; end: Date | null };
|
|
12
|
+
export type DatePickerValue = DateValue | RangeValue;
|
|
13
|
+
|
|
14
|
+
export type DatePickerContextType = {
|
|
15
|
+
mode: DatePickerMode;
|
|
16
|
+
|
|
17
|
+
// Raw state shared across layers.
|
|
18
|
+
value: DatePickerValue;
|
|
19
|
+
draftBuffer: DatePickerValue;
|
|
20
|
+
setCommittedValue: (value: DatePickerValue) => void;
|
|
21
|
+
setDraftBuffer: (value: DatePickerValue) => void;
|
|
22
|
+
|
|
23
|
+
// User's explicit `draft` prop on <DatePicker.Provider>. Undefined if not passed.
|
|
24
|
+
providerDraft: boolean | undefined;
|
|
25
|
+
|
|
26
|
+
// Resolved for this layer — surfaces override these via withDraftLayer.
|
|
27
|
+
draft: boolean;
|
|
28
|
+
draftValue: DatePickerValue;
|
|
29
|
+
setValue: (value: DatePickerValue) => void;
|
|
30
|
+
commit: () => void;
|
|
31
|
+
cancel: () => void;
|
|
32
|
+
|
|
33
|
+
// Bypass setter for typed-entry fields — always commits, ignores draft.
|
|
34
|
+
commitValue: (value: DatePickerValue) => void;
|
|
35
|
+
|
|
36
|
+
open: boolean;
|
|
37
|
+
setOpen: (open: boolean) => void;
|
|
38
|
+
triggerRef: RefObject<any>;
|
|
39
|
+
locale?: DatePickerLocale;
|
|
40
|
+
validRange?: ValidRangeType;
|
|
41
|
+
is24Hour: boolean;
|
|
42
|
+
dateFormat: string;
|
|
43
|
+
disabled?: boolean;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/** Rebuilds draft-derived fields on top of a base context for a specific layer's draft mode. */
|
|
47
|
+
export function withDraftLayer(base: DatePickerContextType, draft: boolean): DatePickerContextType {
|
|
48
|
+
return {
|
|
49
|
+
...base,
|
|
50
|
+
draft,
|
|
51
|
+
draftValue: draft ? base.draftBuffer : base.value,
|
|
52
|
+
setValue: next => {
|
|
53
|
+
if (draft) base.setDraftBuffer(next);
|
|
54
|
+
else base.setCommittedValue(next);
|
|
55
|
+
},
|
|
56
|
+
commit: () => {
|
|
57
|
+
if (draft) base.setCommittedValue(base.draftBuffer);
|
|
58
|
+
base.setOpen(false);
|
|
59
|
+
},
|
|
60
|
+
cancel: () => {
|
|
61
|
+
base.setDraftBuffer(base.value);
|
|
62
|
+
base.setOpen(false);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export const DatePickerContext = createContext<DatePickerContextType | null>(null);
|
|
68
|
+
|
|
69
|
+
export function useDatePickerContext(): DatePickerContextType {
|
|
70
|
+
const ctx = useContext(DatePickerContext);
|
|
71
|
+
if (!ctx) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
'useDatePickerContext must be used within a <DatePickerProvider>. Wrap your date/time components with <DatePickerProvider>.',
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return ctx;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function useOptionalDatePickerContext(): DatePickerContextType | null {
|
|
80
|
+
return useContext(DatePickerContext);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
registerPortalContext(DatePickerContext);
|