@himakarinv/nativex 1.0.0

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 (170) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +105 -0
  3. package/images.d.ts +14 -0
  4. package/index.js +8 -0
  5. package/nativewind-preset.js +18 -0
  6. package/package.json +120 -0
  7. package/src/assets/branding/logo.jpg +0 -0
  8. package/src/assets/branding/softech-logo.png +0 -0
  9. package/src/assets/icons/2-layers.svg +3 -0
  10. package/src/assets/icons/Forbidden.svg +211 -0
  11. package/src/assets/icons/Internal-server-error.svg +119 -0
  12. package/src/assets/icons/Log-out-nav.svg +4 -0
  13. package/src/assets/icons/Not-Found.svg +282 -0
  14. package/src/assets/icons/Unauthorized.svg +314 -0
  15. package/src/assets/icons/User-Frame.svg +5 -0
  16. package/src/assets/icons/alert.svg +4 -0
  17. package/src/assets/icons/arrow-right.svg +3 -0
  18. package/src/assets/icons/avatar-female.svg +10 -0
  19. package/src/assets/icons/avatar-male.svg +10 -0
  20. package/src/assets/icons/bell.svg +3 -0
  21. package/src/assets/icons/calendar-event.svg +11 -0
  22. package/src/assets/icons/calendar.svg +6 -0
  23. package/src/assets/icons/card.svg +4 -0
  24. package/src/assets/icons/caret-right.svg +3 -0
  25. package/src/assets/icons/chat.svg +11 -0
  26. package/src/assets/icons/check.svg +3 -0
  27. package/src/assets/icons/checkbox-minus.svg +3 -0
  28. package/src/assets/icons/checkmark.svg +3 -0
  29. package/src/assets/icons/chevron-down.svg +3 -0
  30. package/src/assets/icons/chevron-left.svg +9 -0
  31. package/src/assets/icons/chevron-right.svg +9 -0
  32. package/src/assets/icons/circle-radio-active.svg +10 -0
  33. package/src/assets/icons/circle-radio.svg +10 -0
  34. package/src/assets/icons/clock.svg +3 -0
  35. package/src/assets/icons/close.svg +3 -0
  36. package/src/assets/icons/code.svg +3 -0
  37. package/src/assets/icons/country.svg +20 -0
  38. package/src/assets/icons/dot.svg +3 -0
  39. package/src/assets/icons/download.svg +1 -0
  40. package/src/assets/icons/dropdown-arrow.svg +3 -0
  41. package/src/assets/icons/dropdown-avatar.svg +3 -0
  42. package/src/assets/icons/email.svg +3 -0
  43. package/src/assets/icons/error.svg +3 -0
  44. package/src/assets/icons/eye-off.svg +1 -0
  45. package/src/assets/icons/eye.svg +1 -0
  46. package/src/assets/icons/hashtag-icon.svg +3 -0
  47. package/src/assets/icons/help-circle.svg +10 -0
  48. package/src/assets/icons/help.svg +4 -0
  49. package/src/assets/icons/hide-checkbox.svg +3 -0
  50. package/src/assets/icons/home-smile.svg +3 -0
  51. package/src/assets/icons/home.svg +3 -0
  52. package/src/assets/icons/info.svg +5 -0
  53. package/src/assets/icons/left-arrow.svg +3 -0
  54. package/src/assets/icons/log-out.svg +3 -0
  55. package/src/assets/icons/notification-box.svg +4 -0
  56. package/src/assets/icons/pencil.svg +1 -0
  57. package/src/assets/icons/plus.svg +3 -0
  58. package/src/assets/icons/radio-dot.svg +3 -0
  59. package/src/assets/icons/right-arrow.svg +3 -0
  60. package/src/assets/icons/search.svg +3 -0
  61. package/src/assets/icons/settings.svg +11 -0
  62. package/src/assets/icons/slack.svg +17 -0
  63. package/src/assets/icons/slash.svg +3 -0
  64. package/src/assets/icons/success.svg +3 -0
  65. package/src/assets/icons/successicon.svg +5 -0
  66. package/src/assets/icons/three-dot-vertical.svg +5 -0
  67. package/src/assets/icons/trash.svg +1 -0
  68. package/src/assets/icons/trend-down-01.svg +3 -0
  69. package/src/assets/icons/trend-up-01.svg +3 -0
  70. package/src/assets/icons/up-arrow.svg +3 -0
  71. package/src/assets/icons/user-plus.svg +10 -0
  72. package/src/assets/icons/user.svg +3 -0
  73. package/src/assets/icons/users.svg +3 -0
  74. package/src/assets/icons/vertical-dots-true-icon.svg +5 -0
  75. package/src/assets/icons/vertical-line.svg +3 -0
  76. package/src/assets/icons/vertical-three-dots-icon.svg +5 -0
  77. package/src/assets/icons/warning.svg +3 -0
  78. package/src/assets/icons/zap.svg +3 -0
  79. package/src/components/ActionsMenu/ActionsMenu.tsx +314 -0
  80. package/src/components/ActionsMenu/index.ts +2 -0
  81. package/src/components/Alert/Alert.tsx +291 -0
  82. package/src/components/Alert/index.ts +2 -0
  83. package/src/components/Badge/Badge.tsx +230 -0
  84. package/src/components/Badge/index.ts +2 -0
  85. package/src/components/Breadcrumb/Breadcrumb.tsx +364 -0
  86. package/src/components/Breadcrumb/index.ts +7 -0
  87. package/src/components/Button/Button.tsx +584 -0
  88. package/src/components/Button/index.ts +9 -0
  89. package/src/components/Checkbox/Checkbox.tsx +336 -0
  90. package/src/components/Checkbox/index.ts +2 -0
  91. package/src/components/CreatePassword/CreatePassword.tsx +161 -0
  92. package/src/components/CreatePassword/index.ts +2 -0
  93. package/src/components/DataTable/DataTable.tsx +822 -0
  94. package/src/components/DataTable/index.ts +10 -0
  95. package/src/components/Datepicker/Datepicker.tsx +797 -0
  96. package/src/components/Datepicker/index.ts +2 -0
  97. package/src/components/Dropdown/Dropdown.tsx +277 -0
  98. package/src/components/Dropdown/index.ts +7 -0
  99. package/src/components/DropdownMenu/DropdownMenu.tsx +471 -0
  100. package/src/components/DropdownMenu/index.ts +11 -0
  101. package/src/components/DynamicForm/DynamicForm.tsx +741 -0
  102. package/src/components/DynamicForm/index.ts +13 -0
  103. package/src/components/ErrorPages/ErrorPages.tsx +135 -0
  104. package/src/components/ErrorPages/index.ts +2 -0
  105. package/src/components/ForgotPassword/ForgotPassword.tsx +121 -0
  106. package/src/components/ForgotPassword/index.ts +2 -0
  107. package/src/components/HintError/HintError.tsx +37 -0
  108. package/src/components/HintError/index.ts +2 -0
  109. package/src/components/Icon/Icon.tsx +43 -0
  110. package/src/components/Icon/icon-registry.ts +227 -0
  111. package/src/components/Icon/index.ts +3 -0
  112. package/src/components/InputDropdown/InputDropdown.tsx +662 -0
  113. package/src/components/InputDropdown/index.ts +9 -0
  114. package/src/components/InputField/InputField.tsx +527 -0
  115. package/src/components/InputField/index.ts +2 -0
  116. package/src/components/Label/Label.tsx +88 -0
  117. package/src/components/Label/index.ts +2 -0
  118. package/src/components/LoginForm/LoginForm.tsx +158 -0
  119. package/src/components/LoginForm/index.ts +2 -0
  120. package/src/components/LoginShell/LoginShell.tsx +97 -0
  121. package/src/components/LoginShell/RoleCard.tsx +24 -0
  122. package/src/components/LoginShell/SoftechLogo.tsx +24 -0
  123. package/src/components/LoginShell/branding-assets.ts +2 -0
  124. package/src/components/LoginShell/index.ts +20 -0
  125. package/src/components/LoginShell/login-copy.ts +45 -0
  126. package/src/components/LoginShell/loginPageStyles.ts +210 -0
  127. package/src/components/Modals/Modals.tsx +313 -0
  128. package/src/components/Modals/index.ts +2 -0
  129. package/src/components/Offcanvas/Offcanvas.tsx +404 -0
  130. package/src/components/Offcanvas/index.ts +13 -0
  131. package/src/components/PageHeader/PageHeader.tsx +128 -0
  132. package/src/components/PageHeader/index.ts +2 -0
  133. package/src/components/Pagination/Pagination.tsx +540 -0
  134. package/src/components/Pagination/index.ts +8 -0
  135. package/src/components/ResetPassword/ResetPassword.tsx +172 -0
  136. package/src/components/ResetPassword/index.ts +2 -0
  137. package/src/components/RoleSelection/RoleSelection.tsx +111 -0
  138. package/src/components/RoleSelection/index.ts +2 -0
  139. package/src/components/SendOtp/SendOtp.tsx +149 -0
  140. package/src/components/SendOtp/index.ts +2 -0
  141. package/src/components/Sidenavbar/Sidenavbar.tsx +358 -0
  142. package/src/components/Sidenavbar/index.ts +2 -0
  143. package/src/components/StatCard/StatCard.tsx +393 -0
  144. package/src/components/StatCard/index.ts +6 -0
  145. package/src/components/Tabs/Tabs.tsx +239 -0
  146. package/src/components/Tabs/index.ts +2 -0
  147. package/src/components/Textarea/Textarea.tsx +217 -0
  148. package/src/components/Textarea/index.ts +2 -0
  149. package/src/components/Timepicker/Timepicker.tsx +384 -0
  150. package/src/components/Timepicker/index.ts +2 -0
  151. package/src/components/Toast/Toast.tsx +278 -0
  152. package/src/components/Toast/index.ts +2 -0
  153. package/src/components/Toggle/Toggle.tsx +296 -0
  154. package/src/components/Toggle/index.ts +2 -0
  155. package/src/components/Typeahead/Typeahead.tsx +521 -0
  156. package/src/components/Typeahead/index.ts +2 -0
  157. package/src/components/Verification/Verification.tsx +374 -0
  158. package/src/components/Verification/index.ts +2 -0
  159. package/src/index.ts +39 -0
  160. package/src/theme/borders.ts +19 -0
  161. package/src/theme/button.ts +41 -0
  162. package/src/theme/colors.ts +273 -0
  163. package/src/theme/index.ts +14 -0
  164. package/src/theme/nativewind-theme.js +182 -0
  165. package/src/theme/radius.ts +12 -0
  166. package/src/theme/shadows.ts +40 -0
  167. package/src/theme/spacing.ts +18 -0
  168. package/src/theme/typography.ts +35 -0
  169. package/src/utils/a11y.ts +21 -0
  170. package/svg.d.ts +7 -0
@@ -0,0 +1,797 @@
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import {
3
+ Dimensions,
4
+ Modal,
5
+ Platform,
6
+ Pressable,
7
+ StyleSheet,
8
+ Text,
9
+ View,
10
+ type StyleProp,
11
+ type ViewStyle,
12
+ } from 'react-native';
13
+
14
+ import { borders, colors, radius, spacing, typography } from '../../theme';
15
+ import { Button, ButtonSize, ButtonVariant, IconPosition } from '../Button';
16
+ import { Icon, IconName } from '../Icon';
17
+ import {
18
+ InputDropdown,
19
+ InputDropdownSize,
20
+ buildMonthDropdownOptions,
21
+ buildYearDropdownOptions,
22
+ type InputDropdownItem,
23
+ } from '../InputDropdown';
24
+ import { InputField } from '../InputField';
25
+ import { Label } from '../Label';
26
+
27
+ export const DatepickerMode = {
28
+ Single: 'single',
29
+ Range: 'range',
30
+ } as const;
31
+
32
+ export type DatepickerMode = (typeof DatepickerMode)[keyof typeof DatepickerMode];
33
+
34
+ export type DatepickerDate = {
35
+ year: number;
36
+ month: number;
37
+ day: number;
38
+ };
39
+
40
+ export type DatepickerDateRange = {
41
+ from: DatepickerDate | null;
42
+ to: DatepickerDate | null;
43
+ };
44
+
45
+ export type DatepickerProps = {
46
+ mode?: DatepickerMode;
47
+ label?: string;
48
+ required?: boolean;
49
+ formatHint?: string;
50
+ placeholder?: string;
51
+ date?: DatepickerDate | null;
52
+ fromDate?: DatepickerDate | null;
53
+ toDate?: DatepickerDate | null;
54
+ firstDayOfWeek?: number;
55
+ cancelLabel?: string;
56
+ applyLabel?: string;
57
+ error?: string;
58
+ disabled?: boolean;
59
+ readOnly?: boolean;
60
+ fullWidth?: boolean;
61
+ onDateChange?: (date: DatepickerDate | null) => void;
62
+ onRangeChange?: (range: DatepickerDateRange) => void;
63
+ style?: StyleProp<ViewStyle>;
64
+ };
65
+
66
+ const isNative = Platform.OS !== 'web';
67
+ const FIELD_WIDTH = isNative ? 255 : 320;
68
+ const FIELD_HEIGHT = isNative ? 36 : 44;
69
+ const MENU_GAP = spacing.xs;
70
+ const WEEKDAYS = ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'];
71
+
72
+ const monthOptions = buildMonthDropdownOptions();
73
+ const yearOptions = buildYearDropdownOptions(1980, new Date().getFullYear() + 50);
74
+
75
+ function pad2(value: number) {
76
+ return String(value).padStart(2, '0');
77
+ }
78
+
79
+ function cloneDate(date: DatepickerDate | null | undefined): DatepickerDate | null {
80
+ if (!date || !Number.isFinite(date.year) || !Number.isFinite(date.month) || !Number.isFinite(date.day)) {
81
+ return null;
82
+ }
83
+ return { year: date.year, month: date.month, day: date.day };
84
+ }
85
+
86
+ function sameDate(left: DatepickerDate | null, right: DatepickerDate | null) {
87
+ if (!left || !right) {
88
+ return false;
89
+ }
90
+ return left.year === right.year && left.month === right.month && left.day === right.day;
91
+ }
92
+
93
+ function toKey(date: DatepickerDate) {
94
+ return `${date.year}-${pad2(date.month)}-${pad2(date.day)}`;
95
+ }
96
+
97
+ function compareDate(left: DatepickerDate, right: DatepickerDate) {
98
+ const a = left.year * 10000 + left.month * 100 + left.day;
99
+ const b = right.year * 10000 + right.month * 100 + right.day;
100
+ return a - b;
101
+ }
102
+
103
+ function formatMdy(date: DatepickerDate | null) {
104
+ if (!date) {
105
+ return '';
106
+ }
107
+ return `${pad2(date.month)}/${pad2(date.day)}/${date.year}`;
108
+ }
109
+
110
+ function formatRange(from: DatepickerDate | null, to: DatepickerDate | null) {
111
+ const start = formatMdy(from);
112
+ const end = formatMdy(to);
113
+ if (start && end) {
114
+ return `${start} - ${end}`;
115
+ }
116
+ return start;
117
+ }
118
+
119
+ function addMonths(year: number, month: number, delta: number) {
120
+ const next = new Date(year, month - 1 + delta, 1);
121
+ return { year: next.getFullYear(), month: next.getMonth() + 1 };
122
+ }
123
+
124
+ function weekdayIndex(year: number, month: number, day: number) {
125
+ return new Date(year, month - 1, day).getDay();
126
+ }
127
+
128
+ function buildCells(year: number, month: number, firstDayOfWeek: number) {
129
+ const lead = (weekdayIndex(year, month, 1) - firstDayOfWeek + 7) % 7;
130
+ const days = new Date(year, month, 0).getDate();
131
+ const prev = addMonths(year, month, -1);
132
+ const prevDays = new Date(prev.year, prev.month, 0).getDate();
133
+ const next = addMonths(year, month, 1);
134
+ const cells: { date: DatepickerDate; outside: boolean }[] = [];
135
+
136
+ for (let i = lead; i > 0; i -= 1) {
137
+ cells.push({ date: { year: prev.year, month: prev.month, day: prevDays - i + 1 }, outside: true });
138
+ }
139
+ for (let day = 1; day <= days; day += 1) {
140
+ cells.push({ date: { year, month, day }, outside: false });
141
+ }
142
+ while (cells.length % 7 !== 0) {
143
+ cells.push({
144
+ date: { year: next.year, month: next.month, day: cells.length - lead - days + 1 },
145
+ outside: true,
146
+ });
147
+ }
148
+ return cells;
149
+ }
150
+
151
+ function webPanelShadow(): ViewStyle {
152
+ return {
153
+ boxShadow: `0 ${spacing.xs}px ${spacing.md}px ${colors.boxShadowLight}`,
154
+ } as ViewStyle;
155
+ }
156
+
157
+ function panelMetrics(triggerWidth: number) {
158
+ const window = Dimensions.get('window');
159
+ const pad = spacing.lg;
160
+ const available = Math.max(spacing['7xl'] * 4, window.width - pad * 2);
161
+ const preferred = triggerWidth > spacing.none ? triggerWidth : FIELD_WIDTH;
162
+ const width = Math.min(available, preferred);
163
+ const inner = Math.max(7, width - spacing.md * 2);
164
+ const cell = Math.floor(inner / 7);
165
+ return { width, cell };
166
+ }
167
+
168
+ function menuPosition(
169
+ trigger: { x: number; y: number; width: number; height: number },
170
+ menuHeight: number,
171
+ panelWidth: number,
172
+ ) {
173
+ const window = Dimensions.get('window');
174
+ const pad = spacing.lg;
175
+ const left = Math.min(Math.max(pad, trigger.x), Math.max(pad, window.width - panelWidth - pad));
176
+ const below = trigger.y + trigger.height + MENU_GAP;
177
+ const above = trigger.y - menuHeight - MENU_GAP;
178
+ const top = below + menuHeight + pad <= window.height ? below : Math.max(pad, above);
179
+ return { top, left };
180
+ }
181
+
182
+ export function Datepicker({
183
+ mode = DatepickerMode.Single,
184
+ label = 'Select Date',
185
+ required = false,
186
+ formatHint = '(mm/dd/yyyy)',
187
+ placeholder = 'mm/dd/yyyy',
188
+ date,
189
+ fromDate,
190
+ toDate,
191
+ firstDayOfWeek = 1,
192
+ cancelLabel = 'Cancel',
193
+ applyLabel = 'Apply',
194
+ error = '',
195
+ disabled = false,
196
+ readOnly = false,
197
+ fullWidth = false,
198
+ onDateChange,
199
+ onRangeChange,
200
+ style,
201
+ }: DatepickerProps) {
202
+ const isRange = mode === DatepickerMode.Range;
203
+ const [open, setOpen] = useState(false);
204
+ const [value, setValue] = useState<DatepickerDate | null>(() => cloneDate(date));
205
+ const [draft, setDraft] = useState<DatepickerDate | null>(() => cloneDate(date));
206
+ const [rangeFrom, setRangeFrom] = useState<DatepickerDate | null>(() => cloneDate(fromDate));
207
+ const [rangeTo, setRangeTo] = useState<DatepickerDate | null>(() => cloneDate(toDate));
208
+ const [snapshot, setSnapshot] = useState<DatepickerDate | null>(null);
209
+ const [rangeFromSnap, setRangeFromSnap] = useState<DatepickerDate | null>(null);
210
+ const [rangeToSnap, setRangeToSnap] = useState<DatepickerDate | null>(null);
211
+ const [navYear, setNavYear] = useState(() => String(date?.year ?? fromDate?.year ?? new Date().getFullYear()));
212
+ const [navMonth, setNavMonth] = useState(() => pad2(date?.month ?? fromDate?.month ?? new Date().getMonth() + 1));
213
+ const [triggerBox, setTriggerBox] = useState({ x: 0, y: 0, width: 0, height: 0 });
214
+ const [menuHeight, setMenuHeight] = useState(0);
215
+ const hostRef = useRef<View>(null);
216
+ const controlRef = useRef<View>(null);
217
+
218
+ useEffect(() => {
219
+ setValue(cloneDate(date));
220
+ setDraft(cloneDate(date));
221
+ }, [date?.year, date?.month, date?.day]);
222
+
223
+ useEffect(() => {
224
+ setRangeFrom(cloneDate(fromDate));
225
+ }, [fromDate?.year, fromDate?.month, fromDate?.day]);
226
+
227
+ useEffect(() => {
228
+ setRangeTo(cloneDate(toDate));
229
+ }, [toDate?.year, toDate?.month, toDate?.day]);
230
+
231
+ const weekdayLabels = useMemo(() => {
232
+ const start = ((firstDayOfWeek % 7) + 7) % 7;
233
+ return WEEKDAYS.slice(start).concat(WEEKDAYS.slice(0, start));
234
+ }, [firstDayOfWeek]);
235
+
236
+ const cells = useMemo(
237
+ () => buildCells(Number(navYear), Number(navMonth), firstDayOfWeek),
238
+ [firstDayOfWeek, navMonth, navYear],
239
+ );
240
+
241
+ const display = isRange ? formatRange(rangeFrom, rangeTo) : formatMdy(value);
242
+ const fieldLocked = disabled || readOnly;
243
+
244
+ const syncNav = (next: DatepickerDate | null) => {
245
+ const source = next ?? {
246
+ year: new Date().getFullYear(),
247
+ month: new Date().getMonth() + 1,
248
+ day: 1,
249
+ };
250
+ setNavYear(String(source.year));
251
+ setNavMonth(pad2(source.month));
252
+ };
253
+
254
+ const close = () => setOpen(false);
255
+
256
+ const revealCalendar = () => {
257
+ if (isRange) {
258
+ setRangeFromSnap(cloneDate(rangeFrom));
259
+ setRangeToSnap(cloneDate(rangeTo));
260
+ syncNav(rangeFrom ?? rangeTo);
261
+ } else {
262
+ setSnapshot(cloneDate(value));
263
+ setDraft(cloneDate(value));
264
+ syncNav(value);
265
+ }
266
+ const target = controlRef.current ?? hostRef.current;
267
+ if (!target) {
268
+ setOpen(true);
269
+ return;
270
+ }
271
+ target.measureInWindow((x, y, width, height) => {
272
+ setTriggerBox({
273
+ x,
274
+ y,
275
+ width: width > spacing.none ? width : FIELD_WIDTH,
276
+ height: height > spacing.none ? height : FIELD_HEIGHT,
277
+ });
278
+ setOpen(true);
279
+ });
280
+ };
281
+
282
+ const cancel = () => {
283
+ if (isRange) {
284
+ setRangeFrom(cloneDate(rangeFromSnap));
285
+ setRangeTo(cloneDate(rangeToSnap));
286
+ } else {
287
+ setDraft(cloneDate(snapshot));
288
+ }
289
+ close();
290
+ };
291
+
292
+ const apply = () => {
293
+ if (isRange) {
294
+ onRangeChange?.({ from: rangeFrom, to: rangeTo });
295
+ } else {
296
+ setValue(cloneDate(draft));
297
+ onDateChange?.(cloneDate(draft));
298
+ }
299
+ close();
300
+ };
301
+
302
+ const toggleCalendar = () => {
303
+ if (fieldLocked) {
304
+ return;
305
+ }
306
+ if (open) {
307
+ cancel();
308
+ return;
309
+ }
310
+ revealCalendar();
311
+ };
312
+
313
+ const shiftMonth = (delta: number) => {
314
+ const next = addMonths(Number(navYear), Number(navMonth), delta);
315
+ setNavYear(String(next.year));
316
+ setNavMonth(pad2(next.month));
317
+ };
318
+
319
+ const selectDay = (next: DatepickerDate) => {
320
+ if (isRange) {
321
+ if (!rangeFrom || (rangeFrom && rangeTo)) {
322
+ setRangeFrom(next);
323
+ setRangeTo(null);
324
+ return;
325
+ }
326
+ if (compareDate(next, rangeFrom) < 0) {
327
+ setRangeFrom(next);
328
+ setRangeTo(null);
329
+ return;
330
+ }
331
+ setRangeTo(next);
332
+ return;
333
+ }
334
+ setDraft(next);
335
+ };
336
+
337
+ const isSelected = (cell: DatepickerDate) => {
338
+ if (isRange) {
339
+ return sameDate(cell, rangeFrom) || sameDate(cell, rangeTo);
340
+ }
341
+ return sameDate(cell, draft);
342
+ };
343
+
344
+ const isInside = (cell: DatepickerDate) => {
345
+ if (!isRange || !rangeFrom || !rangeTo) {
346
+ return false;
347
+ }
348
+ return compareDate(cell, rangeFrom) > 0 && compareDate(cell, rangeTo) < 0;
349
+ };
350
+
351
+ const metrics = panelMetrics(triggerBox.width || FIELD_WIDTH);
352
+ const position = menuPosition(triggerBox, menuHeight, metrics.width);
353
+ const inlineNav = metrics.width >= spacing['4xl'] * 2 + (spacing['7xl'] * 2 + spacing['2xl']) * 2 + spacing.md * 3;
354
+ const monthYearSelects = (
355
+ <View style={styles.monthYearRow}>
356
+ <InputDropdown
357
+ size={InputDropdownSize.XSmall}
358
+ suffix={false}
359
+ label=""
360
+ placeholder="Month"
361
+ options={monthOptions}
362
+ selectedValue={navMonth}
363
+ onSelectionChange={(item: InputDropdownItem | null) => {
364
+ if (item?.value) {
365
+ setNavMonth(item.value);
366
+ }
367
+ }}
368
+ />
369
+ <InputDropdown
370
+ size={InputDropdownSize.XSmall}
371
+ suffix={false}
372
+ label=""
373
+ placeholder="Year"
374
+ options={yearOptions}
375
+ selectedValue={navYear}
376
+ onSelectionChange={(item: InputDropdownItem | null) => {
377
+ if (item?.value) {
378
+ setNavYear(item.value);
379
+ }
380
+ }}
381
+ />
382
+ </View>
383
+ );
384
+ const controlBorder = error
385
+ ? colors.datepicker.fieldErrorBorder
386
+ : open
387
+ ? colors.datepicker.fieldFocusBorder
388
+ : colors.datepicker.fieldBorder;
389
+
390
+ return (
391
+ <View ref={hostRef} collapsable={false} style={[styles.host, fullWidth && styles.fullWidth, style]}>
392
+ <Label
393
+ text={label}
394
+ supportingText={formatHint}
395
+ required={required}
396
+ embedded
397
+ htmlFor="datepicker-field"
398
+ style={styles.labelRow}
399
+ labelStyle={styles.labelText}
400
+ supportingStyle={styles.hintText}
401
+ />
402
+
403
+ <Pressable
404
+ ref={controlRef}
405
+ collapsable={false}
406
+ accessibilityRole="button"
407
+ accessibilityLabel={label}
408
+ accessibilityState={{ disabled: fieldLocked, expanded: open }}
409
+ disabled={fieldLocked}
410
+ onPress={toggleCalendar}
411
+ style={[
412
+ styles.control,
413
+ { borderColor: controlBorder, borderWidth: open && !error ? borders.width.medium : borders.width.thin },
414
+ disabled && styles.controlDisabled,
415
+ readOnly && !disabled && styles.controlReadonly,
416
+ ]}>
417
+ <View style={styles.inputEmbed} pointerEvents="none">
418
+ <InputField
419
+ suppressLabel
420
+ suppressHintError
421
+ readOnly
422
+ fullWidth
423
+ datepickerFieldNativeLook
424
+ disabled={disabled}
425
+ value={display}
426
+ placeholder={placeholder}
427
+ accessibilityLabel={label}
428
+ style={styles.inputField}
429
+ />
430
+ </View>
431
+ <View pointerEvents="none" style={styles.calendarBtnWrap}>
432
+ <Button
433
+ label=""
434
+ variant={ButtonVariant.TertiaryGray}
435
+ size={ButtonSize.Small}
436
+ iconPosition={IconPosition.Only}
437
+ iconName={IconName.Calendar}
438
+ iconDecorative
439
+ disabled={fieldLocked}
440
+ accessibilityLabel="Open calendar"
441
+ contentStyle={styles.calendarBtn}
442
+ />
443
+ </View>
444
+ </Pressable>
445
+
446
+ {error ? (
447
+ <View style={styles.errorRow} accessibilityRole="alert">
448
+ <Icon name={IconName.Alert} size={spacing.lg} color={colors.datepicker.fieldErrorText} decorative />
449
+ <Text style={styles.errorText}>{error}</Text>
450
+ </View>
451
+ ) : null}
452
+
453
+ {open ? (
454
+ <Modal visible transparent animationType="none" onRequestClose={cancel}>
455
+ <Pressable accessible={false} style={styles.backdrop} onPress={cancel}>
456
+ <Pressable
457
+ accessible={false}
458
+ onPress={(event) => {
459
+ if ('stopPropagation' in event && typeof event.stopPropagation === 'function') {
460
+ event.stopPropagation();
461
+ }
462
+ }}
463
+ onLayout={(event) => {
464
+ const nextHeight = event.nativeEvent.layout.height;
465
+ if (nextHeight !== menuHeight) {
466
+ setMenuHeight(nextHeight);
467
+ }
468
+ }}
469
+ style={[
470
+ styles.panel,
471
+ { top: position.top, left: position.left, width: metrics.width },
472
+ !isNative && webPanelShadow(),
473
+ ]}>
474
+ <View style={styles.header}>
475
+ {inlineNav ? (
476
+ <View style={styles.navInline}>
477
+ <Button
478
+ label=""
479
+ variant={ButtonVariant.TertiaryGray}
480
+ size={ButtonSize.Small}
481
+ iconPosition={IconPosition.Only}
482
+ iconName={IconName.ChevronLeft}
483
+ iconDecorative
484
+ accessibilityLabel="Previous month"
485
+ onPress={() => shiftMonth(-1)}
486
+ contentStyle={styles.navBtn}
487
+ />
488
+ <View style={styles.navSelectsInline}>{monthYearSelects}</View>
489
+ <Button
490
+ label=""
491
+ variant={ButtonVariant.TertiaryGray}
492
+ size={ButtonSize.Small}
493
+ iconPosition={IconPosition.Only}
494
+ iconName={IconName.ChevronRight}
495
+ iconDecorative
496
+ accessibilityLabel="Next month"
497
+ onPress={() => shiftMonth(1)}
498
+ contentStyle={styles.navBtn}
499
+ />
500
+ </View>
501
+ ) : (
502
+ <View style={styles.navStack}>
503
+ <View style={styles.navRow}>
504
+ <Button
505
+ label=""
506
+ variant={ButtonVariant.TertiaryGray}
507
+ size={ButtonSize.Small}
508
+ iconPosition={IconPosition.Only}
509
+ iconName={IconName.ChevronLeft}
510
+ iconDecorative
511
+ accessibilityLabel="Previous month"
512
+ onPress={() => shiftMonth(-1)}
513
+ contentStyle={styles.navBtn}
514
+ />
515
+ <Button
516
+ label=""
517
+ variant={ButtonVariant.TertiaryGray}
518
+ size={ButtonSize.Small}
519
+ iconPosition={IconPosition.Only}
520
+ iconName={IconName.ChevronRight}
521
+ iconDecorative
522
+ accessibilityLabel="Next month"
523
+ onPress={() => shiftMonth(1)}
524
+ contentStyle={styles.navBtn}
525
+ />
526
+ </View>
527
+ <View style={styles.navSelectsStack}>{monthYearSelects}</View>
528
+ </View>
529
+ )}
530
+ </View>
531
+
532
+ <View style={styles.weekdays}>
533
+ {weekdayLabels.map((day) => (
534
+ <Text key={day} style={[styles.weekday, { width: metrics.cell }]}>
535
+ {day}
536
+ </Text>
537
+ ))}
538
+ </View>
539
+
540
+ <View style={[styles.grid, { width: metrics.cell * 7 }]}>
541
+ {cells.map((cell) => {
542
+ const selected = isSelected(cell.date);
543
+ const inside = isInside(cell.date);
544
+ const isFrom = isRange && sameDate(cell.date, rangeFrom);
545
+ const isTo = isRange && sameDate(cell.date, rangeTo);
546
+ return (
547
+ <Pressable
548
+ key={toKey(cell.date)}
549
+ accessibilityRole="button"
550
+ accessibilityLabel={formatMdy(cell.date)}
551
+ accessibilityState={{ selected }}
552
+ onPress={() => selectDay(cell.date)}
553
+ style={[
554
+ styles.day,
555
+ { width: metrics.cell, height: metrics.cell },
556
+ inside && styles.dayInside,
557
+ selected && styles.daySelected,
558
+ isFrom && !isTo && styles.dayFrom,
559
+ isTo && !isFrom && styles.dayTo,
560
+ ]}>
561
+ <Text
562
+ style={[
563
+ styles.dayText,
564
+ cell.outside && styles.dayOutside,
565
+ inside && styles.dayInsideText,
566
+ selected && styles.daySelectedText,
567
+ ]}>
568
+ {cell.date.day}
569
+ </Text>
570
+ </Pressable>
571
+ );
572
+ })}
573
+ </View>
574
+
575
+ <View style={styles.footer}>
576
+ <Button
577
+ label={cancelLabel}
578
+ variant={ButtonVariant.SecondaryGray}
579
+ size={ButtonSize.Small}
580
+ onPress={cancel}
581
+ />
582
+ <Button label={applyLabel} variant={ButtonVariant.Primary} size={ButtonSize.Small} onPress={apply} />
583
+ </View>
584
+ </Pressable>
585
+ </Pressable>
586
+ </Modal>
587
+ ) : null}
588
+ </View>
589
+ );
590
+ }
591
+
592
+ const styles = StyleSheet.create({
593
+ host: {
594
+ width: FIELD_WIDTH,
595
+ maxWidth: '100%',
596
+ alignSelf: 'flex-start',
597
+ position: 'relative',
598
+ gap: spacing.sm + spacing.xs,
599
+ },
600
+ fullWidth: {
601
+ width: '100%',
602
+ },
603
+ labelRow: {
604
+ flexDirection: 'row',
605
+ flexWrap: 'wrap',
606
+ alignItems: 'baseline',
607
+ gap: spacing.xs,
608
+ },
609
+ labelText: {
610
+ color: colors.datepicker.fieldText,
611
+ fontWeight: typography.fontWeight.semibold,
612
+ },
613
+ hintText: {
614
+ color: colors.datepicker.fieldLabelMuted,
615
+ fontWeight: typography.fontWeight.regular,
616
+ },
617
+ control: {
618
+ width: '100%',
619
+ height: FIELD_HEIGHT,
620
+ minHeight: FIELD_HEIGHT,
621
+ maxHeight: FIELD_HEIGHT,
622
+ flexDirection: 'row',
623
+ alignItems: 'center',
624
+ alignSelf: 'stretch',
625
+ gap: spacing.xs,
626
+ paddingVertical: spacing.none,
627
+ paddingHorizontal: spacing.base,
628
+ borderRadius: radius.md,
629
+ backgroundColor: colors.datepicker.fieldSurface,
630
+ overflow: 'hidden',
631
+ },
632
+ controlDisabled: {
633
+ backgroundColor: colors.datepicker.fieldDisabledSurface,
634
+ borderColor: colors.datepicker.fieldDisabledBorder,
635
+ opacity: 0.7,
636
+ },
637
+ controlReadonly: {
638
+ backgroundColor: colors.datepicker.fieldReadonlySurface,
639
+ },
640
+ inputEmbed: {
641
+ flex: 1,
642
+ minWidth: spacing.none,
643
+ height: FIELD_HEIGHT,
644
+ maxHeight: FIELD_HEIGHT,
645
+ justifyContent: 'center',
646
+ overflow: 'hidden',
647
+ },
648
+ inputField: {
649
+ width: '100%',
650
+ height: FIELD_HEIGHT,
651
+ maxHeight: FIELD_HEIGHT,
652
+ },
653
+ calendarBtnWrap: {
654
+ width: spacing['4xl'],
655
+ height: spacing['4xl'],
656
+ alignItems: 'center',
657
+ justifyContent: 'center',
658
+ },
659
+ calendarBtn: {
660
+ width: spacing['4xl'],
661
+ height: spacing['4xl'],
662
+ minHeight: spacing['4xl'],
663
+ padding: spacing.none,
664
+ },
665
+ monthYearRow: {
666
+ flexDirection: 'row',
667
+ flexWrap: 'wrap',
668
+ alignItems: 'center',
669
+ justifyContent: 'center',
670
+ gap: spacing.sm,
671
+ },
672
+ errorRow: {
673
+ flexDirection: 'row',
674
+ alignItems: 'flex-start',
675
+ gap: spacing.xs,
676
+ },
677
+ errorText: {
678
+ flex: 1,
679
+ color: colors.datepicker.fieldErrorText,
680
+ fontFamily: typography.fontFamily.base,
681
+ fontSize: typography.fontSize.sm,
682
+ lineHeight: typography.fontSize.sm * typography.lineHeight.tight,
683
+ },
684
+ backdrop: {
685
+ flex: 1,
686
+ },
687
+ panel: {
688
+ position: 'absolute',
689
+ backgroundColor: colors.white,
690
+ borderWidth: borders.width.thin,
691
+ borderColor: colors.datepicker.fieldBorder,
692
+ borderRadius: radius.md,
693
+ overflow: 'hidden',
694
+ },
695
+ header: {
696
+ paddingHorizontal: spacing.md,
697
+ paddingVertical: spacing.sm,
698
+ borderBottomWidth: borders.width.thin,
699
+ borderBottomColor: colors.datepicker.fieldBorder,
700
+ },
701
+ navInline: {
702
+ flexDirection: 'row',
703
+ alignItems: 'center',
704
+ justifyContent: 'space-between',
705
+ gap: spacing.xs,
706
+ },
707
+ navStack: {
708
+ gap: spacing.sm,
709
+ },
710
+ navBtn: {
711
+ width: spacing['4xl'],
712
+ height: spacing['4xl'],
713
+ minHeight: spacing['4xl'],
714
+ padding: spacing.none,
715
+ },
716
+ navSelectsInline: {
717
+ flex: 1,
718
+ minWidth: spacing.none,
719
+ alignItems: 'center',
720
+ },
721
+ navSelectsStack: {
722
+ width: '100%',
723
+ minWidth: spacing.none,
724
+ alignItems: 'center',
725
+ },
726
+ navRow: {
727
+ width: '100%',
728
+ flexDirection: 'row',
729
+ alignItems: 'center',
730
+ justifyContent: 'space-between',
731
+ },
732
+ weekdays: {
733
+ flexDirection: 'row',
734
+ justifyContent: 'center',
735
+ paddingHorizontal: spacing.md,
736
+ paddingTop: spacing.sm,
737
+ },
738
+ weekday: {
739
+ textAlign: 'center',
740
+ color: colors.datepicker.fieldText,
741
+ fontFamily: typography.fontFamily.base,
742
+ fontSize: typography.fontSize.sm,
743
+ fontWeight: typography.fontWeight.semibold,
744
+ },
745
+ grid: {
746
+ flexDirection: 'row',
747
+ flexWrap: 'wrap',
748
+ alignSelf: 'center',
749
+ paddingBottom: spacing.sm,
750
+ },
751
+ day: {
752
+ alignItems: 'center',
753
+ justifyContent: 'center',
754
+ },
755
+ dayInside: {
756
+ backgroundColor: colors.datepicker.rangeBg,
757
+ },
758
+ daySelected: {
759
+ backgroundColor: colors.datepicker.popupCellSelectedBg,
760
+ borderRadius: radius.sm,
761
+ },
762
+ dayFrom: {
763
+ borderTopLeftRadius: radius.sm,
764
+ borderBottomLeftRadius: radius.sm,
765
+ borderTopRightRadius: radius.none,
766
+ borderBottomRightRadius: radius.none,
767
+ },
768
+ dayTo: {
769
+ borderTopRightRadius: radius.sm,
770
+ borderBottomRightRadius: radius.sm,
771
+ borderTopLeftRadius: radius.none,
772
+ borderBottomLeftRadius: radius.none,
773
+ },
774
+ dayText: {
775
+ color: colors.datepicker.popupCellText,
776
+ fontFamily: typography.fontFamily.base,
777
+ fontSize: typography.fontSize.sm,
778
+ },
779
+ dayOutside: {
780
+ color: colors.datepicker.popupCellOutsideText,
781
+ },
782
+ dayInsideText: {
783
+ color: colors.datepicker.rangeText,
784
+ },
785
+ daySelectedText: {
786
+ color: colors.datepicker.popupCellSelectedText,
787
+ },
788
+ footer: {
789
+ flexDirection: 'row',
790
+ justifyContent: 'flex-end',
791
+ gap: spacing.sm,
792
+ paddingHorizontal: spacing.md,
793
+ paddingVertical: spacing.sm,
794
+ borderTopWidth: borders.width.thin,
795
+ borderTopColor: colors.datepicker.fieldBorder,
796
+ },
797
+ });