@cdx-ui/components 0.0.1-beta.140 → 0.0.1-beta.142

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 (148) hide show
  1. package/lib/commonjs/components/BottomSheet/index.js +8 -2
  2. package/lib/commonjs/components/BottomSheet/index.js.map +1 -1
  3. package/lib/commonjs/components/Calendar/CalendarMonthHeader.js +96 -0
  4. package/lib/commonjs/components/Calendar/CalendarMonthHeader.js.map +1 -0
  5. package/lib/commonjs/components/Calendar/CalendarYearPickerGrid.js +74 -0
  6. package/lib/commonjs/components/Calendar/CalendarYearPickerGrid.js.map +1 -0
  7. package/lib/commonjs/components/Calendar/flashCalendarAdapter.js +298 -0
  8. package/lib/commonjs/components/Calendar/flashCalendarAdapter.js.map +1 -0
  9. package/lib/commonjs/components/Calendar/index.js +149 -0
  10. package/lib/commonjs/components/Calendar/index.js.map +1 -0
  11. package/lib/commonjs/components/Calendar/styles.js +70 -0
  12. package/lib/commonjs/components/Calendar/styles.js.map +1 -0
  13. package/lib/commonjs/components/DateField/index.js +147 -0
  14. package/lib/commonjs/components/DateField/index.js.map +1 -0
  15. package/lib/commonjs/components/DateField/styles.js +34 -0
  16. package/lib/commonjs/components/DateField/styles.js.map +1 -0
  17. package/lib/commonjs/components/DatePicker/index.js +163 -0
  18. package/lib/commonjs/components/DatePicker/index.js.map +1 -0
  19. package/lib/commonjs/components/DateRangePicker/index.js +177 -0
  20. package/lib/commonjs/components/DateRangePicker/index.js.map +1 -0
  21. package/lib/commonjs/components/DateTimeField/index.js +96 -0
  22. package/lib/commonjs/components/DateTimeField/index.js.map +1 -0
  23. package/lib/commonjs/components/DateTimeList/index.js +180 -0
  24. package/lib/commonjs/components/DateTimeList/index.js.map +1 -0
  25. package/lib/commonjs/components/DateTimeList/styles.js +55 -0
  26. package/lib/commonjs/components/DateTimeList/styles.js.map +1 -0
  27. package/lib/commonjs/components/DateTimePicker/index.js +187 -0
  28. package/lib/commonjs/components/DateTimePicker/index.js.map +1 -0
  29. package/lib/commonjs/components/MonthCalendar/index.js +229 -0
  30. package/lib/commonjs/components/MonthCalendar/index.js.map +1 -0
  31. package/lib/commonjs/components/MonthPicker/index.js +163 -0
  32. package/lib/commonjs/components/MonthPicker/index.js.map +1 -0
  33. package/lib/commonjs/components/RangeCalendar/index.js +165 -0
  34. package/lib/commonjs/components/RangeCalendar/index.js.map +1 -0
  35. package/lib/commonjs/components/index.js +120 -0
  36. package/lib/commonjs/components/index.js.map +1 -1
  37. package/lib/commonjs/components/picker/PickerTrigger.js +49 -0
  38. package/lib/commonjs/components/picker/PickerTrigger.js.map +1 -0
  39. package/lib/commonjs/components/picker/styles.js +15 -0
  40. package/lib/commonjs/components/picker/styles.js.map +1 -0
  41. package/lib/commonjs/components/picker/surfaces.js +141 -0
  42. package/lib/commonjs/components/picker/surfaces.js.map +1 -0
  43. package/lib/module/components/BottomSheet/index.js +8 -2
  44. package/lib/module/components/BottomSheet/index.js.map +1 -1
  45. package/lib/module/components/Calendar/CalendarMonthHeader.js +92 -0
  46. package/lib/module/components/Calendar/CalendarMonthHeader.js.map +1 -0
  47. package/lib/module/components/Calendar/CalendarYearPickerGrid.js +70 -0
  48. package/lib/module/components/Calendar/CalendarYearPickerGrid.js.map +1 -0
  49. package/lib/module/components/Calendar/flashCalendarAdapter.js +290 -0
  50. package/lib/module/components/Calendar/flashCalendarAdapter.js.map +1 -0
  51. package/lib/module/components/Calendar/index.js +145 -0
  52. package/lib/module/components/Calendar/index.js.map +1 -0
  53. package/lib/module/components/Calendar/styles.js +66 -0
  54. package/lib/module/components/Calendar/styles.js.map +1 -0
  55. package/lib/module/components/DateField/index.js +143 -0
  56. package/lib/module/components/DateField/index.js.map +1 -0
  57. package/lib/module/components/DateField/styles.js +30 -0
  58. package/lib/module/components/DateField/styles.js.map +1 -0
  59. package/lib/module/components/DatePicker/index.js +159 -0
  60. package/lib/module/components/DatePicker/index.js.map +1 -0
  61. package/lib/module/components/DateRangePicker/index.js +173 -0
  62. package/lib/module/components/DateRangePicker/index.js.map +1 -0
  63. package/lib/module/components/DateTimeField/index.js +92 -0
  64. package/lib/module/components/DateTimeField/index.js.map +1 -0
  65. package/lib/module/components/DateTimeList/index.js +176 -0
  66. package/lib/module/components/DateTimeList/index.js.map +1 -0
  67. package/lib/module/components/DateTimeList/styles.js +52 -0
  68. package/lib/module/components/DateTimeList/styles.js.map +1 -0
  69. package/lib/module/components/DateTimePicker/index.js +183 -0
  70. package/lib/module/components/DateTimePicker/index.js.map +1 -0
  71. package/lib/module/components/MonthCalendar/index.js +225 -0
  72. package/lib/module/components/MonthCalendar/index.js.map +1 -0
  73. package/lib/module/components/MonthPicker/index.js +159 -0
  74. package/lib/module/components/MonthPicker/index.js.map +1 -0
  75. package/lib/module/components/RangeCalendar/index.js +161 -0
  76. package/lib/module/components/RangeCalendar/index.js.map +1 -0
  77. package/lib/module/components/index.js +10 -0
  78. package/lib/module/components/index.js.map +1 -1
  79. package/lib/module/components/picker/PickerTrigger.js +45 -0
  80. package/lib/module/components/picker/PickerTrigger.js.map +1 -0
  81. package/lib/module/components/picker/styles.js +12 -0
  82. package/lib/module/components/picker/styles.js.map +1 -0
  83. package/lib/module/components/picker/surfaces.js +135 -0
  84. package/lib/module/components/picker/surfaces.js.map +1 -0
  85. package/lib/typescript/components/BottomSheet/index.d.ts +2 -2
  86. package/lib/typescript/components/BottomSheet/index.d.ts.map +1 -1
  87. package/lib/typescript/components/Calendar/CalendarMonthHeader.d.ts +14 -0
  88. package/lib/typescript/components/Calendar/CalendarMonthHeader.d.ts.map +1 -0
  89. package/lib/typescript/components/Calendar/CalendarYearPickerGrid.d.ts +12 -0
  90. package/lib/typescript/components/Calendar/CalendarYearPickerGrid.d.ts.map +1 -0
  91. package/lib/typescript/components/Calendar/flashCalendarAdapter.d.ts +18 -0
  92. package/lib/typescript/components/Calendar/flashCalendarAdapter.d.ts.map +1 -0
  93. package/lib/typescript/components/Calendar/index.d.ts +19 -0
  94. package/lib/typescript/components/Calendar/index.d.ts.map +1 -0
  95. package/lib/typescript/components/Calendar/styles.d.ts +23 -0
  96. package/lib/typescript/components/Calendar/styles.d.ts.map +1 -0
  97. package/lib/typescript/components/DateField/index.d.ts +43 -0
  98. package/lib/typescript/components/DateField/index.d.ts.map +1 -0
  99. package/lib/typescript/components/DateField/styles.d.ts +15 -0
  100. package/lib/typescript/components/DateField/styles.d.ts.map +1 -0
  101. package/lib/typescript/components/DatePicker/index.d.ts +40 -0
  102. package/lib/typescript/components/DatePicker/index.d.ts.map +1 -0
  103. package/lib/typescript/components/DateRangePicker/index.d.ts +45 -0
  104. package/lib/typescript/components/DateRangePicker/index.d.ts.map +1 -0
  105. package/lib/typescript/components/DateTimeField/index.d.ts +29 -0
  106. package/lib/typescript/components/DateTimeField/index.d.ts.map +1 -0
  107. package/lib/typescript/components/DateTimeList/index.d.ts +19 -0
  108. package/lib/typescript/components/DateTimeList/index.d.ts.map +1 -0
  109. package/lib/typescript/components/DateTimeList/styles.d.ts +17 -0
  110. package/lib/typescript/components/DateTimeList/styles.d.ts.map +1 -0
  111. package/lib/typescript/components/DateTimePicker/index.d.ts +40 -0
  112. package/lib/typescript/components/DateTimePicker/index.d.ts.map +1 -0
  113. package/lib/typescript/components/MonthCalendar/index.d.ts +29 -0
  114. package/lib/typescript/components/MonthCalendar/index.d.ts.map +1 -0
  115. package/lib/typescript/components/MonthPicker/index.d.ts +40 -0
  116. package/lib/typescript/components/MonthPicker/index.d.ts.map +1 -0
  117. package/lib/typescript/components/RangeCalendar/index.d.ts +19 -0
  118. package/lib/typescript/components/RangeCalendar/index.d.ts.map +1 -0
  119. package/lib/typescript/components/index.d.ts +10 -0
  120. package/lib/typescript/components/index.d.ts.map +1 -1
  121. package/lib/typescript/components/picker/PickerTrigger.d.ts +25 -0
  122. package/lib/typescript/components/picker/PickerTrigger.d.ts.map +1 -0
  123. package/lib/typescript/components/picker/styles.d.ts +4 -0
  124. package/lib/typescript/components/picker/styles.d.ts.map +1 -0
  125. package/lib/typescript/components/picker/surfaces.d.ts +28 -0
  126. package/lib/typescript/components/picker/surfaces.d.ts.map +1 -0
  127. package/package.json +6 -4
  128. package/src/components/BottomSheet/index.tsx +7 -1
  129. package/src/components/Calendar/CalendarMonthHeader.tsx +124 -0
  130. package/src/components/Calendar/CalendarYearPickerGrid.tsx +101 -0
  131. package/src/components/Calendar/flashCalendarAdapter.ts +362 -0
  132. package/src/components/Calendar/index.tsx +151 -0
  133. package/src/components/Calendar/styles.ts +80 -0
  134. package/src/components/DateField/index.tsx +196 -0
  135. package/src/components/DateField/styles.ts +51 -0
  136. package/src/components/DatePicker/index.tsx +187 -0
  137. package/src/components/DateRangePicker/index.tsx +202 -0
  138. package/src/components/DateTimeField/index.tsx +128 -0
  139. package/src/components/DateTimeList/index.tsx +187 -0
  140. package/src/components/DateTimeList/styles.ts +59 -0
  141. package/src/components/DateTimePicker/index.tsx +233 -0
  142. package/src/components/MonthCalendar/index.tsx +252 -0
  143. package/src/components/MonthPicker/index.tsx +188 -0
  144. package/src/components/RangeCalendar/index.tsx +162 -0
  145. package/src/components/index.ts +10 -0
  146. package/src/components/picker/PickerTrigger.tsx +65 -0
  147. package/src/components/picker/styles.ts +17 -0
  148. package/src/components/picker/surfaces.tsx +174 -0
@@ -0,0 +1,101 @@
1
+ import { forwardRef, useCallback, useMemo, type ComponentRef } from 'react';
2
+ import { Pressable, Text, View, type ViewProps } from 'react-native';
3
+ import {
4
+ formatMonthId,
5
+ formatSelectYear,
6
+ parseMonthId,
7
+ resolveDateComponentLabels,
8
+ type DateComponentLabels,
9
+ type MonthId,
10
+ } from '@cdx-ui/formatters';
11
+ import { cn } from '@cdx-ui/utils';
12
+ import type { CalendarNavigationView } from './CalendarMonthHeader';
13
+ import {
14
+ calendarPillCellTextVariants,
15
+ calendarPillCellVariants,
16
+ calendarYearPickerGridVariants,
17
+ } from './styles';
18
+
19
+ const YEAR_RANGE_BEFORE = 9;
20
+ const YEAR_RANGE_AFTER = 11;
21
+
22
+ export interface CalendarYearPickerGridProps extends ViewProps {
23
+ monthId: MonthId;
24
+ onMonthIdChange: (monthId: MonthId) => void;
25
+ disabled?: boolean;
26
+ onViewChange: (view: CalendarNavigationView) => void;
27
+ labels?: Partial<DateComponentLabels>;
28
+ }
29
+
30
+ export const CalendarYearPickerGrid = forwardRef<
31
+ ComponentRef<typeof View>,
32
+ CalendarYearPickerGridProps
33
+ >(
34
+ (
35
+ {
36
+ monthId,
37
+ onMonthIdChange,
38
+ disabled = false,
39
+ onViewChange,
40
+ labels: labelsProp,
41
+ className,
42
+ style,
43
+ ...props
44
+ },
45
+ ref,
46
+ ) => {
47
+ const labels = useMemo(() => resolveDateComponentLabels(labelsProp), [labelsProp]);
48
+ const parts = parseMonthId(monthId);
49
+ const currentYear = parts?.year ?? new Date().getFullYear();
50
+ const currentMonth = parts?.month ?? 1;
51
+
52
+ const years = useMemo(
53
+ () =>
54
+ Array.from(
55
+ { length: YEAR_RANGE_BEFORE + YEAR_RANGE_AFTER + 1 },
56
+ (_, i) => currentYear - YEAR_RANGE_BEFORE + i,
57
+ ),
58
+ [currentYear],
59
+ );
60
+
61
+ const selectYear = useCallback(
62
+ (year: number) => {
63
+ if (disabled) {
64
+ return;
65
+ }
66
+ onMonthIdChange(formatMonthId({ year, month: currentMonth }));
67
+ onViewChange('month');
68
+ },
69
+ [currentMonth, disabled, onMonthIdChange, onViewChange],
70
+ );
71
+
72
+ return (
73
+ <View
74
+ ref={ref}
75
+ className={cn(calendarYearPickerGridVariants(), className)}
76
+ style={style}
77
+ accessibilityRole="none"
78
+ {...props}
79
+ >
80
+ {years.map((year) => {
81
+ const selected = year === currentYear;
82
+ return (
83
+ <Pressable
84
+ key={year}
85
+ accessibilityRole="button"
86
+ accessibilityState={{ selected, disabled }}
87
+ accessibilityLabel={formatSelectYear(labels, year)}
88
+ disabled={disabled}
89
+ onPress={() => selectYear(year)}
90
+ className={calendarPillCellVariants({ selected, disabled })}
91
+ >
92
+ <Text className={calendarPillCellTextVariants({ selected })}>{String(year)}</Text>
93
+ </Pressable>
94
+ );
95
+ })}
96
+ </View>
97
+ );
98
+ },
99
+ );
100
+
101
+ CalendarYearPickerGrid.displayName = 'CalendarYearPickerGrid';
@@ -0,0 +1,362 @@
1
+ import { useMemo } from 'react';
2
+ import { useCSSVariable } from 'uniwind';
3
+ import type { CalendarTheme } from '@marceloterreiro/flash-calendar';
4
+ import type { DateId } from '@cdx-ui/formatters';
5
+
6
+ const DAY_SIZE = 36;
7
+ const DAY_RADIUS = DAY_SIZE / 2;
8
+
9
+ const CALENDAR_COLOR_VARS = [
10
+ '--color-surface-action-strong',
11
+ '--color-content-action-on-strong',
12
+ '--color-content-tertiary',
13
+ '--color-surface-action-subtle',
14
+ '--color-content-primary',
15
+ '--color-stroke-action',
16
+ '--color-surface-action-subtle-hover',
17
+ '--color-content-action',
18
+ ];
19
+
20
+ const CALENDAR_LENGTH_VARS = ['--text-sm', '--border-width-selected', '--opacity-disabled'];
21
+
22
+ const DAY_FONT_SIZE_FALLBACK = 14;
23
+ const TODAY_BORDER_WIDTH_FALLBACK = 2;
24
+ const DISABLED_OPACITY_FALLBACK = 0.5;
25
+ const ROOT_FONT_SIZE_PX = 16;
26
+
27
+ interface CalendarColors {
28
+ surfaceActionStrong: string;
29
+ contentActionOnStrong: string;
30
+ contentTertiary: string;
31
+ surfaceActionSubtle: string;
32
+ contentPrimary: string;
33
+ strokeAction: string;
34
+ surfaceActionSubtleHover: string;
35
+ contentAction: string;
36
+ }
37
+
38
+ interface CalendarLengths {
39
+ dayFontSize: number;
40
+ todayBorderWidth: number;
41
+ disabledOpacity: number;
42
+ }
43
+
44
+ function tokenValue(resolved: string | number | undefined, cssVar: string): string {
45
+ return resolved != null ? String(resolved) : `var(${cssVar})`;
46
+ }
47
+
48
+ /**
49
+ * Coerces a length token to the numeric px RN `fontSize`/`borderWidth` require.
50
+ *
51
+ * `useCSSVariable` resolves lengths differently per platform: native returns a
52
+ * number already in px, while web returns the raw CSS string (`0.875rem`). Only
53
+ * the rem form needs the root-font-size conversion — scaling the native value
54
+ * would multiply it a second time.
55
+ */
56
+ function lengthToPx(resolved: string | number | undefined, fallback: number): number {
57
+ if (typeof resolved === 'number') {
58
+ return Number.isFinite(resolved) ? resolved : fallback;
59
+ }
60
+ if (typeof resolved !== 'string') {
61
+ return fallback;
62
+ }
63
+ const trimmed = resolved.trim();
64
+ const numeric = Number.parseFloat(trimmed);
65
+ if (!Number.isFinite(numeric)) {
66
+ return fallback;
67
+ }
68
+ return trimmed.endsWith('rem') ? numeric * ROOT_FONT_SIZE_PX : numeric;
69
+ }
70
+
71
+ function useFlashCalendarColors(): CalendarColors {
72
+ const [
73
+ surfaceActionStrong,
74
+ contentActionOnStrong,
75
+ contentTertiary,
76
+ surfaceActionSubtle,
77
+ contentPrimary,
78
+ strokeAction,
79
+ surfaceActionSubtleHover,
80
+ contentAction,
81
+ ] = useCSSVariable(CALENDAR_COLOR_VARS);
82
+
83
+ return useMemo(
84
+ () => ({
85
+ surfaceActionStrong: tokenValue(surfaceActionStrong, '--color-surface-action-strong'),
86
+ contentActionOnStrong: tokenValue(contentActionOnStrong, '--color-content-action-on-strong'),
87
+ contentTertiary: tokenValue(contentTertiary, '--color-content-tertiary'),
88
+ surfaceActionSubtle: tokenValue(surfaceActionSubtle, '--color-surface-action-subtle'),
89
+ contentPrimary: tokenValue(contentPrimary, '--color-content-primary'),
90
+ strokeAction: tokenValue(strokeAction, '--color-stroke-action'),
91
+ surfaceActionSubtleHover: tokenValue(
92
+ surfaceActionSubtleHover,
93
+ '--color-surface-action-subtle-hover',
94
+ ),
95
+ contentAction: tokenValue(contentAction, '--color-content-action'),
96
+ }),
97
+ [
98
+ contentAction,
99
+ contentActionOnStrong,
100
+ contentPrimary,
101
+ contentTertiary,
102
+ strokeAction,
103
+ surfaceActionStrong,
104
+ surfaceActionSubtle,
105
+ surfaceActionSubtleHover,
106
+ ],
107
+ );
108
+ }
109
+
110
+ function useFlashCalendarLengths(): CalendarLengths {
111
+ const [textSm, borderWidthSelected, opacityDisabled] = useCSSVariable(CALENDAR_LENGTH_VARS);
112
+
113
+ return useMemo(
114
+ () => ({
115
+ dayFontSize: lengthToPx(textSm, DAY_FONT_SIZE_FALLBACK),
116
+ todayBorderWidth: lengthToPx(borderWidthSelected, TODAY_BORDER_WIDTH_FALLBACK),
117
+ disabledOpacity: lengthToPx(opacityDisabled, DISABLED_OPACITY_FALLBACK),
118
+ }),
119
+ [borderWidthSelected, opacityDisabled, textSm],
120
+ );
121
+ }
122
+
123
+ function isSelectedDay(id: string, selectedDateId?: DateId): boolean {
124
+ return selectedDateId !== undefined && id === selectedDateId;
125
+ }
126
+
127
+ function selectedDayStyles(colors: CalendarColors) {
128
+ return {
129
+ container: {
130
+ borderRadius: DAY_RADIUS,
131
+ borderWidth: 0,
132
+ backgroundColor: colors.surfaceActionStrong,
133
+ },
134
+ content: {
135
+ color: colors.contentActionOnStrong,
136
+ fontWeight: '600' as const,
137
+ },
138
+ };
139
+ }
140
+
141
+ export function useFlashCalendarTheme(selectedDateId?: DateId): CalendarTheme {
142
+ const colors = useFlashCalendarColors();
143
+ const lengths = useFlashCalendarLengths();
144
+ const selected = useMemo(() => selectedDayStyles(colors), [colors]);
145
+
146
+ return useMemo(
147
+ () => ({
148
+ rowMonth: {
149
+ container: {
150
+ height: 0,
151
+ paddingVertical: 0,
152
+ overflow: 'hidden' as const,
153
+ },
154
+ content: {
155
+ fontSize: 0,
156
+ height: 0,
157
+ opacity: 0,
158
+ },
159
+ },
160
+ rowWeek: {
161
+ container: {
162
+ paddingBottom: 4,
163
+ },
164
+ },
165
+ itemWeekName: {
166
+ content: {
167
+ fontSize: 13,
168
+ fontWeight: '500' as const,
169
+ color: colors.contentTertiary,
170
+ },
171
+ },
172
+ itemDay: {
173
+ base: () => ({
174
+ container: {
175
+ borderRadius: DAY_RADIUS,
176
+ alignItems: 'center' as const,
177
+ justifyContent: 'center' as const,
178
+ },
179
+ content: {
180
+ fontSize: lengths.dayFontSize,
181
+ fontWeight: '500' as const,
182
+ },
183
+ }),
184
+ idle: ({ id, isPressed }) =>
185
+ isSelectedDay(id, selectedDateId)
186
+ ? selected
187
+ : {
188
+ container: {
189
+ borderRadius: DAY_RADIUS,
190
+ backgroundColor: isPressed ? colors.surfaceActionSubtle : 'transparent',
191
+ },
192
+ content: {
193
+ color: colors.contentPrimary,
194
+ },
195
+ },
196
+ today: ({ id, isPressed }) =>
197
+ isSelectedDay(id, selectedDateId)
198
+ ? selected
199
+ : {
200
+ container: {
201
+ borderRadius: DAY_RADIUS,
202
+ borderWidth: lengths.todayBorderWidth,
203
+ borderColor: colors.strokeAction,
204
+ backgroundColor: isPressed ? colors.surfaceActionSubtle : 'transparent',
205
+ },
206
+ content: {
207
+ color: colors.contentPrimary,
208
+ fontWeight: '600' as const,
209
+ },
210
+ },
211
+ active: () => selected,
212
+ disabled: () => ({
213
+ container: {
214
+ borderRadius: DAY_RADIUS,
215
+ backgroundColor: 'transparent',
216
+ },
217
+ content: {
218
+ color: colors.contentPrimary,
219
+ opacity: lengths.disabledOpacity,
220
+ },
221
+ }),
222
+ },
223
+ }),
224
+ [colors, lengths, selected, selectedDateId],
225
+ );
226
+ }
227
+
228
+ export function useFlashCalendarRangeTheme(range?: {
229
+ startId?: DateId;
230
+ endId?: DateId;
231
+ }): CalendarTheme {
232
+ const colors = useFlashCalendarColors();
233
+ const lengths = useFlashCalendarLengths();
234
+ const selected = useMemo(() => selectedDayStyles(colors), [colors]);
235
+
236
+ return useMemo(
237
+ () => ({
238
+ rowMonth: {
239
+ container: {
240
+ height: 0,
241
+ paddingVertical: 0,
242
+ overflow: 'hidden' as const,
243
+ },
244
+ content: {
245
+ fontSize: 0,
246
+ height: 0,
247
+ opacity: 0,
248
+ },
249
+ },
250
+ rowWeek: {
251
+ container: {
252
+ paddingBottom: 4,
253
+ },
254
+ },
255
+ itemWeekName: {
256
+ content: {
257
+ fontSize: 13,
258
+ fontWeight: '500' as const,
259
+ color: colors.contentTertiary,
260
+ },
261
+ },
262
+ itemDayContainer: {
263
+ activeDayFiller: {
264
+ backgroundColor: colors.surfaceActionSubtleHover,
265
+ },
266
+ },
267
+ itemDay: {
268
+ base: () => ({
269
+ container: {
270
+ borderRadius: 0,
271
+ padding: 0,
272
+ alignItems: 'center' as const,
273
+ justifyContent: 'center' as const,
274
+ },
275
+ content: {
276
+ fontSize: lengths.dayFontSize,
277
+ fontWeight: '500' as const,
278
+ },
279
+ }),
280
+ idle: ({ isPressed }) => ({
281
+ container: {
282
+ borderRadius: DAY_RADIUS,
283
+ backgroundColor: isPressed ? colors.surfaceActionSubtle : 'transparent',
284
+ },
285
+ content: {
286
+ color: colors.contentPrimary,
287
+ },
288
+ }),
289
+ today: ({ id, isPressed }) => {
290
+ const isEndpoint =
291
+ (range?.startId !== undefined && id === range.startId) ||
292
+ (range?.endId !== undefined && id === range.endId);
293
+ if (isEndpoint) {
294
+ return selected;
295
+ }
296
+ return {
297
+ container: {
298
+ borderRadius: DAY_RADIUS,
299
+ borderWidth: lengths.todayBorderWidth,
300
+ borderColor: colors.strokeAction,
301
+ backgroundColor: isPressed ? colors.surfaceActionSubtle : 'transparent',
302
+ },
303
+ content: {
304
+ color: colors.contentPrimary,
305
+ fontWeight: '600' as const,
306
+ },
307
+ };
308
+ },
309
+ active: ({ isStartOfRange, isEndOfRange }) => {
310
+ const isEndpoint = isStartOfRange || isEndOfRange;
311
+ const isSingleDay = isStartOfRange && isEndOfRange;
312
+ return {
313
+ container: {
314
+ padding: 0,
315
+ borderRadius: isSingleDay ? DAY_RADIUS : 0,
316
+ borderTopLeftRadius: isStartOfRange ? DAY_RADIUS : 0,
317
+ borderBottomLeftRadius: isStartOfRange ? DAY_RADIUS : 0,
318
+ borderTopRightRadius: isEndOfRange ? DAY_RADIUS : 0,
319
+ borderBottomRightRadius: isEndOfRange ? DAY_RADIUS : 0,
320
+ backgroundColor: isEndpoint
321
+ ? colors.surfaceActionStrong
322
+ : colors.surfaceActionSubtleHover,
323
+ },
324
+ content: {
325
+ color: isEndpoint ? colors.contentActionOnStrong : colors.contentAction,
326
+ fontWeight: isEndpoint ? ('600' as const) : ('500' as const),
327
+ },
328
+ };
329
+ },
330
+ disabled: () => ({
331
+ container: {
332
+ borderRadius: DAY_RADIUS,
333
+ backgroundColor: 'transparent',
334
+ },
335
+ content: {
336
+ color: colors.contentPrimary,
337
+ opacity: lengths.disabledOpacity,
338
+ },
339
+ }),
340
+ },
341
+ }),
342
+ [colors, lengths, range?.endId, range?.startId, selected],
343
+ );
344
+ }
345
+
346
+ /** Keep range days flush so the band reads as one shape (filler covers residual sub-pixel gaps). */
347
+ export const FLASH_CALENDAR_RANGE_DAY_SPACING = 0;
348
+
349
+ export const FLASH_CALENDAR_DAY_HEIGHT = DAY_SIZE;
350
+
351
+ export function formatFlashCalendarWeekdayShort(date: Date, locale: string): string {
352
+ return new Intl.DateTimeFormat(locale, { weekday: 'short' }).format(date);
353
+ }
354
+
355
+ /**
356
+ * Brands a raw Flash Calendar day id as a wire {@link DateId}. Flash Calendar
357
+ * always hands back `YYYY-MM-DD`, so this is the trusted third-party boundary
358
+ * where the wire brand is applied.
359
+ */
360
+ export function toCalendarDateId(id: string): DateId {
361
+ return id as DateId;
362
+ }
@@ -0,0 +1,151 @@
1
+ import { forwardRef, useCallback, useId, useMemo, type ReactNode } from 'react';
2
+ import { View, type ViewProps } from 'react-native';
3
+ import { Calendar as FlashCalendar } from '@marceloterreiro/flash-calendar';
4
+ import {
5
+ CalendarGrid as CalendarGridPrimitive,
6
+ CalendarRoot as CalendarRootPrimitive,
7
+ dataAttributes,
8
+ mergeDataAttributes,
9
+ useCalendarContext,
10
+ type ICalendarProps,
11
+ } from '@cdx-ui/primitives';
12
+ import { cn } from '@cdx-ui/utils';
13
+ import { CalendarMonthHeader } from './CalendarMonthHeader';
14
+ import { CalendarYearPickerGrid } from './CalendarYearPickerGrid';
15
+ import {
16
+ FLASH_CALENDAR_DAY_HEIGHT,
17
+ formatFlashCalendarWeekdayShort,
18
+ toCalendarDateId,
19
+ useFlashCalendarTheme,
20
+ } from './flashCalendarAdapter';
21
+ import { calendarRootVariants } from './styles';
22
+
23
+ export interface CalendarProps extends ICalendarProps, Omit<ViewProps, 'children'> {
24
+ className?: string;
25
+ children?: ReactNode;
26
+ }
27
+
28
+ function CalendarDefaultBody() {
29
+ const { view } = useCalendarContext();
30
+ return (
31
+ <>
32
+ <CalendarHeader />
33
+ {view === 'year' ? <CalendarYearPicker /> : <CalendarGrid />}
34
+ </>
35
+ );
36
+ }
37
+
38
+ const CalendarRoot = forwardRef<View, CalendarProps>(
39
+ ({ className, children, style, ...props }, ref) => (
40
+ <CalendarRootPrimitive
41
+ ref={ref}
42
+ className={cn(calendarRootVariants(), className)}
43
+ style={style}
44
+ {...props}
45
+ {...mergeDataAttributes(props, dataAttributes({ slot: 'calendar' }))}
46
+ >
47
+ {children ?? <CalendarDefaultBody />}
48
+ </CalendarRootPrimitive>
49
+ ),
50
+ );
51
+
52
+ CalendarRoot.displayName = 'Calendar';
53
+
54
+ const CalendarHeader = forwardRef<View, ViewProps>(({ className, style, ...props }, ref) => {
55
+ const { monthId, onMonthIdChange, disabled, view, setView, locale, labels } =
56
+ useCalendarContext();
57
+ return (
58
+ <CalendarMonthHeader
59
+ ref={ref}
60
+ monthId={monthId}
61
+ onMonthIdChange={onMonthIdChange}
62
+ disabled={disabled}
63
+ view={view}
64
+ onViewChange={setView}
65
+ locale={locale}
66
+ labels={labels}
67
+ className={className}
68
+ style={style}
69
+ {...props}
70
+ />
71
+ );
72
+ });
73
+
74
+ CalendarHeader.displayName = 'Calendar.Header';
75
+
76
+ const CalendarGrid = forwardRef<View, ViewProps>(({ className, style, ...props }, ref) => {
77
+ const { value, onValueChange, monthId, minValue, maxValue, disabled, locale } =
78
+ useCalendarContext();
79
+ const instanceId = useId();
80
+ const theme = useFlashCalendarTheme(value);
81
+
82
+ const formatWeekday = useCallback(
83
+ (date: Date) => formatFlashCalendarWeekdayShort(date, locale),
84
+ [locale],
85
+ );
86
+
87
+ const activeRanges = useMemo(() => (value ? [{ startId: value, endId: value }] : []), [value]);
88
+
89
+ const handleDayPress = useCallback(
90
+ (dateId: string) => {
91
+ if (!disabled) {
92
+ onValueChange?.(toCalendarDateId(dateId));
93
+ }
94
+ },
95
+ [disabled, onValueChange],
96
+ );
97
+
98
+ return (
99
+ <CalendarGridPrimitive ref={ref} className={className} style={style} {...props}>
100
+ <FlashCalendar
101
+ calendarInstanceId={instanceId}
102
+ calendarMonthId={monthId}
103
+ calendarActiveDateRanges={activeRanges}
104
+ calendarMinDateId={minValue}
105
+ calendarMaxDateId={maxValue}
106
+ calendarMonthHeaderHeight={0}
107
+ calendarDayHeight={FLASH_CALENDAR_DAY_HEIGHT}
108
+ calendarWeekHeaderHeight={28}
109
+ calendarRowVerticalSpacing={4}
110
+ calendarRowHorizontalSpacing={4}
111
+ getCalendarMonthFormat={() => ''}
112
+ getCalendarWeekDayFormat={formatWeekday}
113
+ onCalendarDayPress={handleDayPress}
114
+ theme={theme}
115
+ />
116
+ </CalendarGridPrimitive>
117
+ );
118
+ });
119
+
120
+ CalendarGrid.displayName = 'Calendar.Grid';
121
+
122
+ const CalendarYearPicker = forwardRef<View, ViewProps>(({ className, style, ...props }, ref) => {
123
+ const { monthId, onMonthIdChange, disabled, setView, labels } = useCalendarContext();
124
+ return (
125
+ <CalendarYearPickerGrid
126
+ ref={ref}
127
+ monthId={monthId}
128
+ onMonthIdChange={onMonthIdChange}
129
+ disabled={disabled}
130
+ onViewChange={setView}
131
+ labels={labels}
132
+ className={className}
133
+ style={style}
134
+ {...props}
135
+ />
136
+ );
137
+ });
138
+
139
+ CalendarYearPicker.displayName = 'Calendar.YearPicker';
140
+
141
+ type CalendarCompound = typeof CalendarRoot & {
142
+ Header: typeof CalendarHeader;
143
+ Grid: typeof CalendarGrid;
144
+ YearPicker: typeof CalendarYearPicker;
145
+ };
146
+
147
+ export const Calendar = Object.assign(CalendarRoot, {
148
+ Header: CalendarHeader,
149
+ Grid: CalendarGrid,
150
+ YearPicker: CalendarYearPicker,
151
+ }) as CalendarCompound;
@@ -0,0 +1,80 @@
1
+ import { cva, type VariantProps } from '@cdx-ui/utils';
2
+ import { DISABLED_CURSOR, DISABLED_OPACITY } from '../../styles/constants';
3
+
4
+ export const calendarRootVariants = cva([
5
+ 'gap-3 rounded-[var(--border-radius-default)] bg-surface-primary p-3',
6
+ DISABLED_OPACITY,
7
+ DISABLED_CURSOR,
8
+ ]);
9
+
10
+ export const calendarMonthHeaderVariants = cva('flex-row items-center justify-between gap-2 px-1');
11
+
12
+ export const calendarMonthHeaderToggleVariants = cva('min-w-0 flex-1 flex-row items-center gap-1');
13
+
14
+ export const calendarMonthHeaderLabelVariants = cva('body-lg font-semibold shrink', {
15
+ variants: {
16
+ isYearView: {
17
+ true: 'text-content-action',
18
+ false: 'text-content-primary',
19
+ },
20
+ },
21
+ defaultVariants: { isYearView: false },
22
+ });
23
+
24
+ export const calendarMonthHeaderIconVariants = cva('size-5 shrink-0', {
25
+ variants: {
26
+ isYearView: {
27
+ true: 'text-content-action',
28
+ false: 'text-content-secondary',
29
+ },
30
+ },
31
+ defaultVariants: { isYearView: false },
32
+ });
33
+
34
+ export const calendarMonthHeaderNavVariants = cva('flex-row items-center gap-1');
35
+
36
+ export const calendarYearPickerGridVariants = cva(
37
+ 'flex-row flex-wrap justify-between gap-y-2 px-1 py-1',
38
+ );
39
+
40
+ export const calendarPillCellVariants = cva(
41
+ 'my-0.5 w-[31%] items-center justify-center rounded-[var(--border-radius-round)] py-2.5',
42
+ {
43
+ variants: {
44
+ selected: {
45
+ true: 'bg-surface-action-strong',
46
+ false: 'bg-transparent',
47
+ },
48
+ // Dims out-of-range / disabled cells and gates the compound variant
49
+ // below so hover is suppressed on disabled cells.
50
+ disabled: {
51
+ true: DISABLED_OPACITY,
52
+ false: '',
53
+ },
54
+ },
55
+ compoundVariants: [
56
+ {
57
+ selected: false,
58
+ disabled: false,
59
+ className: 'web:hover:bg-surface-action-subtle',
60
+ },
61
+ ],
62
+ defaultVariants: { selected: false, disabled: false },
63
+ },
64
+ );
65
+
66
+ export const calendarPillCellTextVariants = cva('body-md', {
67
+ variants: {
68
+ selected: {
69
+ true: 'font-semibold text-content-action-on-strong',
70
+ false: 'text-content-primary',
71
+ },
72
+ },
73
+ defaultVariants: { selected: false },
74
+ });
75
+
76
+ export type CalendarRootVariantProps = VariantProps<typeof calendarRootVariants>;
77
+ export type CalendarMonthHeaderLabelVariantProps = VariantProps<
78
+ typeof calendarMonthHeaderLabelVariants
79
+ >;
80
+ export type CalendarPillCellVariantProps = VariantProps<typeof calendarPillCellVariants>;