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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/components/Accordion/Accordion.tsx +2 -6
  2. package/components/Accordion/AccordionItem.tsx +16 -12
  3. package/components/Accordion/AccordionItemContent.tsx +6 -1
  4. package/components/Accordion/AccordionItemHeader.tsx +1 -1
  5. package/components/Accordion/utils.ts +6 -0
  6. package/components/ActivityIndicator/ActivityIndicator.tsx +6 -15
  7. package/components/Appbar/AppbarBase.tsx +18 -13
  8. package/components/Button/Button.tsx +211 -264
  9. package/components/Button/index.tsx +9 -3
  10. package/components/Button/types.ts +16 -2
  11. package/components/Button/utils.ts +230 -208
  12. package/components/Card/Card.tsx +1 -1
  13. package/components/Checkbox/Checkbox.tsx +125 -88
  14. package/components/Checkbox/CheckboxBase.ios.tsx +14 -23
  15. package/components/Checkbox/CheckboxBase.tsx +21 -137
  16. package/components/Checkbox/context.tsx +14 -0
  17. package/components/Checkbox/index.tsx +11 -4
  18. package/components/Checkbox/types.ts +63 -29
  19. package/components/Checkbox/utils.ts +25 -108
  20. package/components/Chip/Chip.tsx +41 -52
  21. package/components/Chip/utils.ts +3 -7
  22. package/components/DateField/DateField.tsx +111 -0
  23. package/components/DateField/index.tsx +6 -0
  24. package/components/{DatePickerInput/inputUtils.ts → DateField/useDateFieldState.ts} +19 -51
  25. package/components/DatePicker/DateCalendar.tsx +83 -0
  26. package/components/DatePicker/DatePickerActions.tsx +73 -0
  27. package/components/DatePicker/DatePickerModal.tsx +246 -0
  28. package/components/DatePicker/DatePickerPopover.tsx +79 -0
  29. package/components/DatePicker/DatePickerProvider.tsx +158 -0
  30. package/components/DatePicker/DatePickerTrigger.tsx +23 -0
  31. package/components/DatePicker/context.tsx +83 -0
  32. package/components/DatePicker/index.tsx +45 -0
  33. package/components/DatePicker/utils.ts +295 -0
  34. package/components/DatePickerInline/DatePickerDockedHeader.tsx +117 -0
  35. package/components/DatePickerInline/DatePickerInline.tsx +17 -16
  36. package/components/DatePickerInline/DatePickerInlineBase.tsx +11 -5
  37. package/components/DatePickerInline/DatePickerInlineHeader.tsx +50 -20
  38. package/components/DatePickerInline/Day.tsx +25 -1
  39. package/components/DatePickerInline/DayNames.tsx +13 -10
  40. package/components/DatePickerInline/DayRange.tsx +2 -4
  41. package/components/DatePickerInline/HeaderItem.tsx +44 -29
  42. package/components/DatePickerInline/Month.tsx +48 -67
  43. package/components/DatePickerInline/MonthPicker.tsx +80 -92
  44. package/components/DatePickerInline/Swiper.native.tsx +21 -4
  45. package/components/DatePickerInline/Swiper.tsx +169 -14
  46. package/components/DatePickerInline/SwiperUtils.ts +1 -1
  47. package/components/DatePickerInline/Week.tsx +6 -1
  48. package/components/DatePickerInline/YearPicker.tsx +220 -78
  49. package/components/DatePickerInline/dateUtils.tsx +18 -13
  50. package/components/DatePickerInline/store.tsx +27 -0
  51. package/components/DatePickerInline/types.ts +6 -2
  52. package/components/DatePickerInline/utils.ts +66 -29
  53. package/components/Divider/Divider.tsx +192 -0
  54. package/components/Divider/index.tsx +10 -0
  55. package/components/Drawer/Drawer.tsx +17 -6
  56. package/components/Drawer/DrawerItemGroup.tsx +3 -7
  57. package/components/ElementGroup/ElementGroup.tsx +1 -1
  58. package/components/FilePicker/FilePicker.tsx +48 -78
  59. package/components/FilePicker/index.tsx +2 -1
  60. package/components/FilePicker/utils.ts +9 -0
  61. package/components/HelperText/HelperText.tsx +0 -35
  62. package/components/Icon/iconFactory.tsx +5 -4
  63. package/components/Icon/index.tsx +1 -1
  64. package/components/Icon/types.ts +17 -6
  65. package/components/IconButton/IconButton.tsx +84 -84
  66. package/components/IconButton/index.tsx +1 -0
  67. package/components/IconButton/types.ts +10 -0
  68. package/components/IconButton/utils.ts +167 -33
  69. package/components/List/List.tsx +276 -0
  70. package/components/List/context.tsx +27 -0
  71. package/components/List/index.ts +8 -0
  72. package/components/List/types.ts +117 -0
  73. package/components/List/utils.ts +79 -0
  74. package/components/LoadingIndicator/LoadingIndicator.tsx +253 -0
  75. package/components/LoadingIndicator/LoadingIndicator.web.tsx +136 -0
  76. package/components/LoadingIndicator/index.tsx +13 -0
  77. package/components/LoadingIndicator/utils.ts +117 -0
  78. package/components/Menu/Menu.tsx +162 -39
  79. package/components/Menu/index.tsx +10 -7
  80. package/components/Menu/utils.ts +21 -70
  81. package/components/NavigationRail/NavigationRail.tsx +15 -9
  82. package/components/Popover/Popover.tsx +119 -145
  83. package/components/Popover/PopoverRoot.tsx +60 -0
  84. package/components/Popover/common.ts +54 -34
  85. package/components/Popover/index.ts +12 -1
  86. package/components/Popover/usePlatformMeasure.native.ts +90 -0
  87. package/components/Popover/usePlatformMeasure.ts +120 -0
  88. package/components/Popover/utils.ts +34 -0
  89. package/components/Portal/Portal.tsx +1 -2
  90. package/components/Radio/Radio.tsx +188 -0
  91. package/components/Radio/RadioBase.ios.tsx +69 -0
  92. package/components/Radio/RadioBase.tsx +136 -0
  93. package/components/Radio/context.tsx +23 -0
  94. package/components/Radio/index.tsx +20 -0
  95. package/components/Radio/types.ts +101 -0
  96. package/components/Radio/utils.ts +115 -0
  97. package/components/Rating/Rating.tsx +1 -1
  98. package/components/Select/Select.tsx +521 -785
  99. package/components/Select/context.tsx +81 -0
  100. package/components/Select/index.ts +26 -14
  101. package/components/Select/types.ts +65 -58
  102. package/components/Select/utils.ts +126 -0
  103. package/components/Slot/Slot.tsx +224 -0
  104. package/components/Slot/compose-refs.tsx +62 -0
  105. package/components/Slot/index.tsx +8 -0
  106. package/components/Surface/Surface.android.tsx +32 -7
  107. package/components/Surface/Surface.ios.tsx +34 -29
  108. package/components/Surface/Surface.tsx +31 -4
  109. package/components/Surface/utils.ts +44 -6
  110. package/components/Switch/Switch.ios.tsx +1 -1
  111. package/components/Switch/Switch.tsx +10 -3
  112. package/components/Tabs/TabItem.tsx +35 -58
  113. package/components/Tabs/TabLabel.tsx +5 -9
  114. package/components/Tabs/Tabs.tsx +156 -150
  115. package/components/Tabs/utils.ts +15 -2
  116. package/components/Text/textFactory.tsx +17 -5
  117. package/components/TextInput/TextInput.tsx +663 -579
  118. package/components/TextInput/index.tsx +19 -3
  119. package/components/TextInput/types.ts +77 -28
  120. package/components/TextInput/utils.ts +235 -145
  121. package/components/TimeField/TimeField.tsx +75 -0
  122. package/components/TimeField/index.tsx +6 -0
  123. package/components/TimeField/useTimeFieldState.ts +70 -0
  124. package/components/{TimePickerField/sanitizeTime.ts → TimeField/utils.ts} +77 -10
  125. package/components/TimePicker/AnalogClock.tsx +1 -1
  126. package/components/TimePicker/TimeInput.tsx +87 -42
  127. package/components/TimePicker/TimeInputs.tsx +138 -50
  128. package/components/TimePicker/TimePicker.tsx +74 -11
  129. package/components/TimePicker/TimePickerModal.tsx +186 -0
  130. package/components/TimePicker/context.tsx +17 -0
  131. package/components/TimePicker/index.tsx +15 -3
  132. package/components/TimePicker/utils.ts +93 -4
  133. package/components/Tooltip/Tooltip.tsx +42 -67
  134. package/components/Tooltip/TooltipContent.tsx +32 -5
  135. package/components/Tooltip/TooltipTrigger.tsx +21 -24
  136. package/components/Tooltip/index.tsx +1 -1
  137. package/components/TouchableRipple/TouchableRipple.native.tsx +83 -16
  138. package/components/TouchableRipple/TouchableRipple.tsx +150 -102
  139. package/components/TouchableRipple/rippleFromForegroundColor.ts +21 -0
  140. package/hocs/index.tsx +1 -1
  141. package/hocs/withKeyboardAccessibility.tsx +2 -3
  142. package/hocs/withPortal.tsx +1 -1
  143. package/hooks/index.tsx +2 -12
  144. package/hooks/useActionState.tsx +19 -8
  145. package/hooks/useContrastColor.ts +1 -2
  146. package/hooks/useFilePicker.tsx +7 -17
  147. package/hooks/useHandleNumberFormat.tsx +2 -2
  148. package/hooks/useMediaQuery.tsx +1 -2
  149. package/package.json +95 -111
  150. package/shortcuts-manager/ShortcutsManager/ShortcutsManager.tsx +6 -3
  151. package/shortcuts-manager/ShortcutsManager/utils.tsx +1 -1
  152. package/shortcuts-manager/useSetScopes/useSetScopes.tsx +1 -1
  153. package/shortcuts-manager/useShortcut/useShortcut.tsx +1 -1
  154. package/styles/shadow.ts +2 -1
  155. package/styles/themes/LightTheme.tsx +1 -1
  156. package/utils/DocumentPicker/documentPicker.ts +78 -27
  157. package/utils/DocumentPicker/types.ts +0 -1
  158. package/utils/extractSubcomponents.ts +89 -0
  159. package/utils/extractTextStyles.ts +1 -2
  160. package/utils/formatNumberWithMask/formatNumberWithMask.ts +2 -1
  161. package/utils/index.ts +0 -3
  162. package/utils/normalizeToNumberString/normalizeToNumberString.ts +1 -1
  163. package/components/DatePickerDocked/DatePickerDocked.tsx +0 -30
  164. package/components/DatePickerDocked/DatePickerDockedHeader.tsx +0 -129
  165. package/components/DatePickerDocked/index.tsx +0 -17
  166. package/components/DatePickerDocked/types.ts +0 -11
  167. package/components/DatePickerDocked/utils.ts +0 -157
  168. package/components/DatePickerInline/DatePickerContext.tsx +0 -21
  169. package/components/DatePickerInput/DatePickerInput.tsx +0 -139
  170. package/components/DatePickerInput/DatePickerInputModal.tsx +0 -48
  171. package/components/DatePickerInput/DatePickerInputWithoutModal.tsx +0 -77
  172. package/components/DatePickerInput/DateRangeInput.tsx +0 -88
  173. package/components/DatePickerInput/index.tsx +0 -10
  174. package/components/DatePickerInput/types.ts +0 -28
  175. package/components/DatePickerInput/utils.ts +0 -15
  176. package/components/DatePickerModal/AnimatedCrossView.tsx +0 -94
  177. package/components/DatePickerModal/CalendarEdit.tsx +0 -139
  178. package/components/DatePickerModal/DatePickerModal.tsx +0 -85
  179. package/components/DatePickerModal/DatePickerModalContent.tsx +0 -155
  180. package/components/DatePickerModal/DatePickerModalContentHeader.tsx +0 -213
  181. package/components/DatePickerModal/DatePickerModalHeader.tsx +0 -74
  182. package/components/DatePickerModal/DatePickerModalHeaderBackground.tsx +0 -13
  183. package/components/DatePickerModal/index.tsx +0 -16
  184. package/components/DatePickerModal/types.ts +0 -92
  185. package/components/DatePickerModal/utils.ts +0 -122
  186. package/components/DateTimePicker/DateTimePicker.tsx +0 -172
  187. package/components/DateTimePicker/index.tsx +0 -10
  188. package/components/DateTimePicker/utils.ts +0 -12
  189. package/components/HorizontalDivider/HorizontalDivider.tsx +0 -103
  190. package/components/HorizontalDivider/index.tsx +0 -9
  191. package/components/ListItem/ListItem.tsx +0 -136
  192. package/components/ListItem/ListItemDescription.tsx +0 -25
  193. package/components/ListItem/ListItemTitle.tsx +0 -25
  194. package/components/ListItem/index.tsx +0 -14
  195. package/components/ListItem/utils.ts +0 -115
  196. package/components/Menu/MenuDivider.tsx +0 -13
  197. package/components/Menu/MenuItem.tsx +0 -128
  198. package/components/Popover/Popover.native.tsx +0 -185
  199. package/components/RadioButton/RadioButton.tsx +0 -138
  200. package/components/RadioButton/RadioButtonAndroid.tsx +0 -188
  201. package/components/RadioButton/RadioButtonGroup.tsx +0 -98
  202. package/components/RadioButton/RadioButtonIOS.tsx +0 -106
  203. package/components/RadioButton/RadioButtonItem.tsx +0 -232
  204. package/components/RadioButton/index.ts +0 -22
  205. package/components/RadioButton/utils.ts +0 -165
  206. package/components/TimePickerField/TimePickerField.tsx +0 -152
  207. package/components/TimePickerField/index.tsx +0 -10
  208. package/components/TimePickerField/utils.ts +0 -94
  209. package/components/TimePickerModal/TimePickerModal.tsx +0 -115
  210. package/components/TimePickerModal/index.tsx +0 -10
  211. package/components/TimePickerModal/utils.ts +0 -47
  212. package/components/VerticalDivider/VerticalDivider.tsx +0 -100
  213. package/components/VerticalDivider/index.tsx +0 -9
  214. package/context-bridge/index.tsx +0 -87
  215. package/fast-context/index.tsx +0 -190
  216. package/hocs/typedMemo.tsx +0 -5
  217. package/hooks/useControlledValue.tsx +0 -68
  218. package/hooks/useLatest.tsx +0 -9
  219. package/hooks/useMergedRefs.ts +0 -14
  220. package/hooks/usePrevious.ts +0 -13
  221. package/hooks/useSearchable.tsx +0 -74
  222. package/hooks/useSubcomponents.tsx +0 -59
  223. package/hooks/useToggle.tsx +0 -24
  224. package/utils/color.ts +0 -22
  225. package/utils/compare/index.ts +0 -54
  226. package/utils/lodash.ts +0 -49
  227. package/utils/repository.ts +0 -53
@@ -1,43 +1,40 @@
1
+ import { isNil } from '@react-native-molecules/utils/helpers/lodash';
2
+ import { useLatest } from '@react-native-molecules/utils/hooks';
1
3
  import { type RefObject, useCallback, useEffect, useState } from 'react';
2
4
  import type { BlurEvent, FocusEvent } from 'react-native';
3
5
 
4
- import { useLatest } from '../../hooks';
5
6
  import { endOfDay, format, isValid, parse } from '../../utils/date-fns';
6
- import { isNil } from '../../utils/lodash';
7
7
  import type { ValidRangeType } from '../DatePickerInline';
8
8
  import { useRangeChecker } from '../DatePickerInline/dateUtils';
9
9
 
10
10
  const formatValue = (value: Date | null | undefined, dateFormat: string) =>
11
11
  !isNil(value) ? format(value, dateFormat) || '' : '';
12
12
 
13
- export default function useDateInput({
14
- // locale,
15
- value,
16
- validRange,
17
- inputMode = 'start',
18
- onChange,
19
- dateFormat,
20
- onBlur: onBlurProp,
21
- onFocus: onFocusProp,
22
- isBlurredRef,
23
- }: {
24
- onChange?: (d: Date | null) => void;
25
- onBlur?: (e: BlurEvent) => void;
26
- onFocus?: (e: FocusEvent) => void;
27
- // locale: undefined | string;
13
+ type Props = {
28
14
  value?: Date | null;
29
15
  validRange?: ValidRangeType;
30
16
  inputMode: 'start' | 'end';
31
17
  dateFormat: string;
32
18
  isBlurredRef: RefObject<boolean>;
33
- }) {
34
- const { isDisabled, isWithinValidRange } = useRangeChecker(validRange);
19
+ onChange?: (value: Date | null) => void;
20
+ onBlur?: (e: BlurEvent) => void;
21
+ onFocus?: (e: FocusEvent) => void;
22
+ };
35
23
 
24
+ export function useDateFieldState({
25
+ value,
26
+ validRange,
27
+ inputMode,
28
+ dateFormat,
29
+ isBlurredRef,
30
+ onChange,
31
+ onBlur: onBlurProp,
32
+ onFocus: onFocusProp,
33
+ }: Props) {
34
+ const { isDisabled, isWithinValidRange } = useRangeChecker(validRange);
36
35
  const [formattedValue, setFormattedValue] = useState(() => formatValue(value, dateFormat));
37
36
  const formattedValueRef = useLatest(formattedValue);
38
37
 
39
- // const [error, setError] = useState<null | string>(null);
40
-
41
38
  const onChangeText = useCallback(
42
39
  (date: string) => {
43
40
  const parsedDate = parse(date, dateFormat, new Date());
@@ -51,45 +48,18 @@ export default function useDateInput({
51
48
  );
52
49
 
53
50
  if (!isValid(parsedDate)) {
54
- // TODO: Translate
55
- // setError(`Date format must be ${dateFormat}`);
56
51
  onChange?.(date ? value ?? null : null);
57
-
58
52
  return;
59
53
  }
60
54
 
61
55
  const finalDate = inputMode === 'end' ? endOfDay(parsedDate) : parsedDate;
62
56
 
63
- if (isDisabled(finalDate)) {
64
- // TODO: Translate
65
- // setError('Day is not allowed');
57
+ if (isDisabled(finalDate) || !isWithinValidRange(finalDate)) {
66
58
  onChange?.(null);
67
-
68
- return;
69
- }
70
- if (!isWithinValidRange(finalDate)) {
71
- // TODO: Translate
72
- // const errors =
73
- // validStart && validEnd
74
- // ? [
75
- // `${`Must be between ${format(validStart, dateFormat)} - ${format(
76
- // validEnd,
77
- // dateFormat,
78
- // )})`}`,
79
- // ]
80
- // : [
81
- // validStart ? `Must be later then ${validStart}` : '',
82
- // validEnd ? `Must be earlier then ${validEnd}` : '',
83
- // ];
84
-
85
- // setError(errors.filter(n => n).join(' '));
86
- onChange?.(null);
87
-
88
59
  return;
89
60
  }
90
61
 
91
62
  onChange?.(finalDate);
92
- // setError(null);
93
63
  },
94
64
  [
95
65
  dateFormat,
@@ -128,8 +98,6 @@ export default function useDateInput({
128
98
  }, [value, dateFormat, isBlurredRef]);
129
99
 
130
100
  return {
131
- onChange,
132
- // error,
133
101
  formattedValue,
134
102
  onChangeText,
135
103
  onBlur,
@@ -0,0 +1,83 @@
1
+ import { memo } from 'react';
2
+
3
+ import { getRegisteredComponentWithFallback } from '../../core';
4
+ import {
5
+ DatePickerInline,
6
+ type DatePickerInlineProps,
7
+ type RangeChange,
8
+ type SingleChange,
9
+ } from '../DatePickerInline';
10
+ import { useOptionalDatePickerContext } from './context';
11
+
12
+ export type DateCalendarProps = DatePickerInlineProps;
13
+
14
+ const DateCalendarDefault = memo(({ headerLayout = 'default', ...props }: DateCalendarProps) => {
15
+ const ctx = useOptionalDatePickerContext();
16
+
17
+ const hasExplicitState =
18
+ props.date !== undefined ||
19
+ props.startDate !== undefined ||
20
+ props.endDate !== undefined ||
21
+ props.dates !== undefined ||
22
+ props.onChange !== undefined;
23
+
24
+ const isRange = props.mode === 'range' || ctx?.mode === 'range';
25
+ const effectiveHeaderLayout = headerLayout ?? (ctx ? 'docked' : 'inline');
26
+ const showOutsideDays =
27
+ props.showOutsideDays ?? (effectiveHeaderLayout === 'docked' && !isRange);
28
+
29
+ if (!ctx || hasExplicitState) {
30
+ return <DatePickerInline {...props} showOutsideDays={showOutsideDays} />;
31
+ }
32
+
33
+ const locale = props.locale ?? ctx.locale;
34
+ const validRange = props.validRange ?? ctx.validRange;
35
+
36
+ if (ctx.mode === 'range') {
37
+ const range =
38
+ ctx.draftValue && typeof ctx.draftValue === 'object' && 'start' in ctx.draftValue
39
+ ? ctx.draftValue
40
+ : { start: null, end: null };
41
+ const onChange: RangeChange = ({ startDate, endDate }) => {
42
+ ctx.setValue({ start: startDate ?? null, end: endDate ?? null });
43
+ };
44
+ return (
45
+ <DatePickerInline
46
+ {...props}
47
+ mode="range"
48
+ startDate={range.start ?? undefined}
49
+ endDate={range.end ?? undefined}
50
+ onChange={onChange}
51
+ locale={locale}
52
+ validRange={validRange}
53
+ headerLayout={headerLayout}
54
+ showOutsideDays={showOutsideDays}
55
+ />
56
+ );
57
+ }
58
+
59
+ const single =
60
+ ctx.draftValue && typeof ctx.draftValue === 'object' && 'start' in ctx.draftValue
61
+ ? null
62
+ : ctx.draftValue;
63
+ const onChange: SingleChange = ({ date }) => {
64
+ ctx.setValue(date ?? null);
65
+ };
66
+
67
+ return (
68
+ <DatePickerInline
69
+ {...props}
70
+ mode="single"
71
+ date={single ?? undefined}
72
+ onChange={onChange}
73
+ locale={locale}
74
+ validRange={validRange}
75
+ headerLayout={headerLayout}
76
+ showOutsideDays={showOutsideDays}
77
+ />
78
+ );
79
+ });
80
+
81
+ DateCalendarDefault.displayName = 'DateCalendar';
82
+
83
+ export const DateCalendar = getRegisteredComponentWithFallback('DateCalendar', DateCalendarDefault);
@@ -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,246 @@
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 type { DatePickerInlineProps } from '../DatePickerInline/DatePickerInline';
15
+ import { IconButton } from '../IconButton';
16
+ import { Modal, type ModalProps } from '../Modal';
17
+ import { Portal } from '../Portal';
18
+ import { Text } from '../Text';
19
+ import { TextInput } from '../TextInput';
20
+ import type { DatePickerContextType, DatePickerLocale, DatePickerValue } from './context';
21
+ import {
22
+ DatePickerContext,
23
+ useDatePickerContext,
24
+ useOptionalDatePickerContext,
25
+ withDraftLayer,
26
+ } from './context';
27
+ import { DateCalendar } from './DateCalendar';
28
+ import { DatePickerActions } from './DatePickerActions';
29
+ import { DatePickerProvider } from './DatePickerProvider';
30
+ import { datePickerModalStyles } from './utils';
31
+
32
+ export type DatePickerModalProps = Omit<ModalProps, 'children' | 'isOpen' | 'onClose'> & {
33
+ children?: ReactNode;
34
+ isOpen?: boolean;
35
+ onClose?: () => void;
36
+ value?: DatePickerValue;
37
+ onChange?: (value: DatePickerValue) => void;
38
+ label?: string;
39
+ disableStatusBar?: boolean;
40
+ disableStatusBarPadding?: boolean;
41
+ confirmLabel?: string;
42
+ cancelLabel?: string;
43
+ editIcon?: string;
44
+ calendarIcon?: string;
45
+ emptySummaryLabel?: string;
46
+ editingSummaryLabel?: string;
47
+ dateInputLabel?: string;
48
+ showCalendarAccessibilityLabel?: string;
49
+ enterDateManuallyAccessibilityLabel?: string;
50
+ locale?: DatePickerLocale;
51
+ headerLayout?: DatePickerInlineProps['headerLayout'];
52
+ /** Override the surface default draft mode. Modal defaults to `true` (staged commit). */
53
+ draft?: boolean;
54
+ };
55
+
56
+ type BodyProps = Omit<DatePickerModalProps, 'isOpen' | 'onClose' | 'value' | 'onChange' | 'draft'>;
57
+
58
+ function DatePickerModalBody({
59
+ children,
60
+ style,
61
+ label = 'Select date',
62
+ disableStatusBar,
63
+ disableStatusBarPadding,
64
+ confirmLabel = 'OK',
65
+ cancelLabel = 'Cancel',
66
+ editIcon = 'pencil',
67
+ calendarIcon = 'calendar',
68
+ emptySummaryLabel = 'Select date',
69
+ editingSummaryLabel = 'Enter dates',
70
+ dateInputLabel = 'Date',
71
+ showCalendarAccessibilityLabel = 'Show calendar',
72
+ enterDateManuallyAccessibilityLabel = 'Enter date manually',
73
+ headerLayout,
74
+ ...rest
75
+ }: BodyProps) {
76
+ const ctx = useDatePickerContext();
77
+ const dimensions = useWindowDimensions();
78
+ const [editing, setEditing] = useState(false);
79
+
80
+ const { containerStyle, headerStyle, barStyle, modalStyle } = useMemo(() => {
81
+ const isHeaderBackgroundLight = true;
82
+ return {
83
+ containerStyle: [StyleSheet.absoluteFill, styles.container],
84
+ headerStyle: [
85
+ datePickerModalStyles.headlineContainer,
86
+ {
87
+ paddingTop:
88
+ disableStatusBarPadding || Platform.OS !== 'android'
89
+ ? datePickerModalStyles.headlineContainer.paddingTop
90
+ : (datePickerModalStyles.headlineContainer.paddingTop as number) +
91
+ (StatusBar.currentHeight || 0),
92
+ },
93
+ ],
94
+ barStyle: (isHeaderBackgroundLight
95
+ ? 'dark-content'
96
+ : 'light-content') as StatusBarStyle,
97
+ modalStyle: [
98
+ datePickerModalStyles.content,
99
+ dimensions.width > 650 ? { maxHeight: 600 } : { borderRadius: 0 },
100
+ style,
101
+ ],
102
+ };
103
+ }, [dimensions.width, disableStatusBarPadding, style]);
104
+
105
+ const draft =
106
+ ctx.draftValue && typeof ctx.draftValue === 'object' && 'start' in ctx.draftValue
107
+ ? null
108
+ : ctx.draftValue;
109
+
110
+ const summaryLabel = useMemo(() => {
111
+ if (editing) return editingSummaryLabel;
112
+ if (!draft) return emptySummaryLabel;
113
+ return new Intl.DateTimeFormat(ctx.locale, {
114
+ weekday: 'short',
115
+ month: 'short',
116
+ day: 'numeric',
117
+ }).format(draft);
118
+ }, [ctx.locale, draft, editing, editingSummaryLabel, emptySummaryLabel]);
119
+
120
+ const body = editing ? (
121
+ <View style={datePickerModalStyles.inputContainer}>
122
+ <DateField autoFocus>
123
+ <TextInput.Label>{dateInputLabel}</TextInput.Label>
124
+ </DateField>
125
+ </View>
126
+ ) : (
127
+ children ?? <DateCalendar headerLayout={headerLayout} showOutsideDays={false} />
128
+ );
129
+
130
+ return (
131
+ <View style={containerStyle} pointerEvents="box-none">
132
+ <Portal>
133
+ <Modal
134
+ {...rest}
135
+ isOpen={ctx.open}
136
+ onClose={ctx.cancel}
137
+ style={modalStyle}
138
+ elevation={0}>
139
+ <>
140
+ {disableStatusBar ? null : (
141
+ <StatusBar translucent={true} barStyle={barStyle} />
142
+ )}
143
+ <View style={datePickerModalStyles.frame}>
144
+ <View style={headerStyle}>
145
+ <View style={datePickerModalStyles.headerCopy}>
146
+ <Text style={datePickerModalStyles.headline}>{label}</Text>
147
+ <Text
148
+ style={datePickerModalStyles.supporting}
149
+ numberOfLines={1}
150
+ adjustsFontSizeToFit={true}>
151
+ {summaryLabel}
152
+ </Text>
153
+ </View>
154
+ <IconButton
155
+ name={editing ? calendarIcon : editIcon}
156
+ accessibilityLabel={
157
+ editing
158
+ ? showCalendarAccessibilityLabel
159
+ : enterDateManuallyAccessibilityLabel
160
+ }
161
+ onPress={() => setEditing(prev => !prev)}
162
+ style={datePickerModalStyles.modeToggle}
163
+ />
164
+ </View>
165
+ <View style={datePickerModalStyles.body}>{body}</View>
166
+ <DatePickerActions
167
+ cancelLabel={cancelLabel}
168
+ confirmLabel={confirmLabel}
169
+ />
170
+ </View>
171
+ </>
172
+ </Modal>
173
+ </Portal>
174
+ </View>
175
+ );
176
+ }
177
+
178
+ function DatePickerModalLayer({
179
+ base,
180
+ draft: draftProp,
181
+ ...rest
182
+ }: BodyProps & {
183
+ base: DatePickerContextType;
184
+ draft: boolean | undefined;
185
+ }) {
186
+ const effectiveDraft = draftProp ?? base.providerDraft ?? true;
187
+ const ctx = useMemo(() => withDraftLayer(base, effectiveDraft), [base, effectiveDraft]);
188
+ if (!base.open) return null;
189
+ return (
190
+ <DatePickerContext value={ctx}>
191
+ <DatePickerModalBody {...rest} />
192
+ </DatePickerContext>
193
+ );
194
+ }
195
+
196
+ function DatePickerModalAdapter({ draft, ...rest }: BodyProps & { draft: boolean | undefined }) {
197
+ const base = useDatePickerContext();
198
+ return <DatePickerModalLayer base={base} draft={draft} {...rest} />;
199
+ }
200
+
201
+ function DatePickerModalInner({
202
+ isOpen: isOpenProp,
203
+ onClose: onCloseProp,
204
+ value: valueProp,
205
+ onChange: onChangeProp,
206
+ locale,
207
+ draft: draftProp,
208
+ ...rest
209
+ }: DatePickerModalProps) {
210
+ const outer = useOptionalDatePickerContext();
211
+
212
+ const onOpenChange = useCallback(
213
+ (next: boolean) => {
214
+ if (!next) onCloseProp?.();
215
+ },
216
+ [onCloseProp],
217
+ );
218
+
219
+ if (outer) {
220
+ return <DatePickerModalLayer base={outer} draft={draftProp} {...rest} />;
221
+ }
222
+
223
+ return (
224
+ <DatePickerProvider
225
+ value={valueProp}
226
+ onChange={onChangeProp}
227
+ open={isOpenProp}
228
+ locale={locale}
229
+ onOpenChange={onOpenChange}>
230
+ <DatePickerModalAdapter draft={draftProp} {...rest} />
231
+ </DatePickerProvider>
232
+ );
233
+ }
234
+
235
+ const styles = StyleSheet.create({
236
+ container: { backgroundColor: 'transparent' },
237
+ });
238
+
239
+ const DatePickerModalDefault = memo(DatePickerModalInner);
240
+
241
+ export default DatePickerModalDefault;
242
+
243
+ export const DatePickerModal = getRegisteredComponentWithFallback(
244
+ 'DatePickerModal',
245
+ DatePickerModalDefault,
246
+ );
@@ -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
+ );