@oxy.so/bloom 4.32.0 → 4.33.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 (173) hide show
  1. package/docs/adoption-matrix.mdx +4 -3
  2. package/docs/date-picker.mdx +15 -28
  3. package/docs/locale.mdx +46 -0
  4. package/docs/provider.mdx +11 -0
  5. package/lib/commonjs/calendar/shared.js +5 -11
  6. package/lib/commonjs/calendar/shared.js.map +1 -1
  7. package/lib/commonjs/date-picker/Calendar.js +11 -2
  8. package/lib/commonjs/date-picker/Calendar.js.map +1 -1
  9. package/lib/commonjs/date-picker/CalendarMonth.js +4 -3
  10. package/lib/commonjs/date-picker/CalendarMonth.js.map +1 -1
  11. package/lib/commonjs/date-picker/DatePicker.js +13 -4
  12. package/lib/commonjs/date-picker/DatePicker.js.map +1 -1
  13. package/lib/commonjs/date-picker/DateRangePicker.js +17 -13
  14. package/lib/commonjs/date-picker/DateRangePicker.js.map +1 -1
  15. package/lib/commonjs/date-picker/MeetingScheduler.js +18 -13
  16. package/lib/commonjs/date-picker/MeetingScheduler.js.map +1 -1
  17. package/lib/commonjs/date-picker/PickerShell.js +5 -5
  18. package/lib/commonjs/date-picker/PickerShell.js.map +1 -1
  19. package/lib/commonjs/date-picker/calendar-grid.js +7 -21
  20. package/lib/commonjs/date-picker/calendar-grid.js.map +1 -1
  21. package/lib/commonjs/date-picker/messages.js +526 -0
  22. package/lib/commonjs/date-picker/messages.js.map +1 -0
  23. package/lib/commonjs/index.js +1063 -1034
  24. package/lib/commonjs/index.js.map +1 -1
  25. package/lib/commonjs/index.web.js +1063 -1034
  26. package/lib/commonjs/index.web.js.map +1 -1
  27. package/lib/commonjs/locale/context.js +35 -0
  28. package/lib/commonjs/locale/context.js.map +1 -0
  29. package/lib/commonjs/locale/format-date.js +34 -0
  30. package/lib/commonjs/locale/format-date.js.map +1 -0
  31. package/lib/commonjs/locale/index.js +32 -0
  32. package/lib/commonjs/locale/index.js.map +1 -0
  33. package/lib/commonjs/locale/languages.js +35 -0
  34. package/lib/commonjs/locale/languages.js.map +1 -0
  35. package/lib/commonjs/locale/messages.js +34 -0
  36. package/lib/commonjs/locale/messages.js.map +1 -0
  37. package/lib/commonjs/locale/plural.js +53 -0
  38. package/lib/commonjs/locale/plural.js.map +1 -0
  39. package/lib/commonjs/provider/BloomProvider.js +18 -13
  40. package/lib/commonjs/provider/BloomProvider.js.map +1 -1
  41. package/lib/module/calendar/shared.js +5 -11
  42. package/lib/module/calendar/shared.js.map +1 -1
  43. package/lib/module/date-picker/Calendar.js +11 -2
  44. package/lib/module/date-picker/Calendar.js.map +1 -1
  45. package/lib/module/date-picker/CalendarMonth.js +4 -3
  46. package/lib/module/date-picker/CalendarMonth.js.map +1 -1
  47. package/lib/module/date-picker/DatePicker.js +13 -4
  48. package/lib/module/date-picker/DatePicker.js.map +1 -1
  49. package/lib/module/date-picker/DateRangePicker.js +17 -13
  50. package/lib/module/date-picker/DateRangePicker.js.map +1 -1
  51. package/lib/module/date-picker/MeetingScheduler.js +18 -13
  52. package/lib/module/date-picker/MeetingScheduler.js.map +1 -1
  53. package/lib/module/date-picker/PickerShell.js +6 -4
  54. package/lib/module/date-picker/PickerShell.js.map +1 -1
  55. package/lib/module/date-picker/calendar-grid.js +7 -21
  56. package/lib/module/date-picker/calendar-grid.js.map +1 -1
  57. package/lib/module/date-picker/messages.js +523 -0
  58. package/lib/module/date-picker/messages.js.map +1 -0
  59. package/lib/module/index.js +1 -0
  60. package/lib/module/index.js.map +1 -1
  61. package/lib/module/index.web.js +1 -0
  62. package/lib/module/index.web.js.map +1 -1
  63. package/lib/module/locale/context.js +30 -0
  64. package/lib/module/locale/context.js.map +1 -0
  65. package/lib/module/locale/format-date.js +30 -0
  66. package/lib/module/locale/format-date.js.map +1 -0
  67. package/lib/module/locale/index.js +7 -0
  68. package/lib/module/locale/index.js.map +1 -0
  69. package/lib/module/locale/languages.js +29 -0
  70. package/lib/module/locale/languages.js.map +1 -0
  71. package/lib/module/locale/messages.js +30 -0
  72. package/lib/module/locale/messages.js.map +1 -0
  73. package/lib/module/locale/plural.js +48 -0
  74. package/lib/module/locale/plural.js.map +1 -0
  75. package/lib/module/provider/BloomProvider.js +16 -11
  76. package/lib/module/provider/BloomProvider.js.map +1 -1
  77. package/lib/typescript/commonjs/calendar/shared.d.ts.map +1 -1
  78. package/lib/typescript/commonjs/date-picker/Calendar.d.ts.map +1 -1
  79. package/lib/typescript/commonjs/date-picker/CalendarMonth.d.ts +5 -3
  80. package/lib/typescript/commonjs/date-picker/CalendarMonth.d.ts.map +1 -1
  81. package/lib/typescript/commonjs/date-picker/DatePicker.d.ts +1 -1
  82. package/lib/typescript/commonjs/date-picker/DatePicker.d.ts.map +1 -1
  83. package/lib/typescript/commonjs/date-picker/DateRangePicker.d.ts +1 -1
  84. package/lib/typescript/commonjs/date-picker/DateRangePicker.d.ts.map +1 -1
  85. package/lib/typescript/commonjs/date-picker/MeetingScheduler.d.ts +1 -1
  86. package/lib/typescript/commonjs/date-picker/MeetingScheduler.d.ts.map +1 -1
  87. package/lib/typescript/commonjs/date-picker/PickerShell.d.ts +3 -3
  88. package/lib/typescript/commonjs/date-picker/PickerShell.d.ts.map +1 -1
  89. package/lib/typescript/commonjs/date-picker/calendar-grid.d.ts +1 -2
  90. package/lib/typescript/commonjs/date-picker/calendar-grid.d.ts.map +1 -1
  91. package/lib/typescript/commonjs/date-picker/messages.d.ts +31 -0
  92. package/lib/typescript/commonjs/date-picker/messages.d.ts.map +1 -0
  93. package/lib/typescript/commonjs/date-picker/types.d.ts +28 -24
  94. package/lib/typescript/commonjs/date-picker/types.d.ts.map +1 -1
  95. package/lib/typescript/commonjs/index.d.ts +2 -0
  96. package/lib/typescript/commonjs/index.d.ts.map +1 -1
  97. package/lib/typescript/commonjs/index.web.d.ts +2 -0
  98. package/lib/typescript/commonjs/index.web.d.ts.map +1 -1
  99. package/lib/typescript/commonjs/locale/context.d.ts +18 -0
  100. package/lib/typescript/commonjs/locale/context.d.ts.map +1 -0
  101. package/lib/typescript/commonjs/locale/format-date.d.ts +13 -0
  102. package/lib/typescript/commonjs/locale/format-date.d.ts.map +1 -0
  103. package/lib/typescript/commonjs/locale/index.d.ts +3 -0
  104. package/lib/typescript/commonjs/locale/index.d.ts.map +1 -0
  105. package/lib/typescript/commonjs/locale/languages.d.ts +16 -0
  106. package/lib/typescript/commonjs/locale/languages.d.ts.map +1 -0
  107. package/lib/typescript/commonjs/locale/messages.d.ts +18 -0
  108. package/lib/typescript/commonjs/locale/messages.d.ts.map +1 -0
  109. package/lib/typescript/commonjs/locale/plural.d.ts +19 -0
  110. package/lib/typescript/commonjs/locale/plural.d.ts.map +1 -0
  111. package/lib/typescript/commonjs/provider/BloomProvider.d.ts +1 -1
  112. package/lib/typescript/commonjs/provider/BloomProvider.d.ts.map +1 -1
  113. package/lib/typescript/commonjs/provider/types.d.ts +6 -0
  114. package/lib/typescript/commonjs/provider/types.d.ts.map +1 -1
  115. package/lib/typescript/module/calendar/shared.d.ts.map +1 -1
  116. package/lib/typescript/module/date-picker/Calendar.d.ts.map +1 -1
  117. package/lib/typescript/module/date-picker/CalendarMonth.d.ts +5 -3
  118. package/lib/typescript/module/date-picker/CalendarMonth.d.ts.map +1 -1
  119. package/lib/typescript/module/date-picker/DatePicker.d.ts +1 -1
  120. package/lib/typescript/module/date-picker/DatePicker.d.ts.map +1 -1
  121. package/lib/typescript/module/date-picker/DateRangePicker.d.ts +1 -1
  122. package/lib/typescript/module/date-picker/DateRangePicker.d.ts.map +1 -1
  123. package/lib/typescript/module/date-picker/MeetingScheduler.d.ts +1 -1
  124. package/lib/typescript/module/date-picker/MeetingScheduler.d.ts.map +1 -1
  125. package/lib/typescript/module/date-picker/PickerShell.d.ts +3 -3
  126. package/lib/typescript/module/date-picker/PickerShell.d.ts.map +1 -1
  127. package/lib/typescript/module/date-picker/calendar-grid.d.ts +1 -2
  128. package/lib/typescript/module/date-picker/calendar-grid.d.ts.map +1 -1
  129. package/lib/typescript/module/date-picker/messages.d.ts +31 -0
  130. package/lib/typescript/module/date-picker/messages.d.ts.map +1 -0
  131. package/lib/typescript/module/date-picker/types.d.ts +28 -24
  132. package/lib/typescript/module/date-picker/types.d.ts.map +1 -1
  133. package/lib/typescript/module/index.d.ts +2 -0
  134. package/lib/typescript/module/index.d.ts.map +1 -1
  135. package/lib/typescript/module/index.web.d.ts +2 -0
  136. package/lib/typescript/module/index.web.d.ts.map +1 -1
  137. package/lib/typescript/module/locale/context.d.ts +18 -0
  138. package/lib/typescript/module/locale/context.d.ts.map +1 -0
  139. package/lib/typescript/module/locale/format-date.d.ts +13 -0
  140. package/lib/typescript/module/locale/format-date.d.ts.map +1 -0
  141. package/lib/typescript/module/locale/index.d.ts +3 -0
  142. package/lib/typescript/module/locale/index.d.ts.map +1 -0
  143. package/lib/typescript/module/locale/languages.d.ts +16 -0
  144. package/lib/typescript/module/locale/languages.d.ts.map +1 -0
  145. package/lib/typescript/module/locale/messages.d.ts +18 -0
  146. package/lib/typescript/module/locale/messages.d.ts.map +1 -0
  147. package/lib/typescript/module/locale/plural.d.ts +19 -0
  148. package/lib/typescript/module/locale/plural.d.ts.map +1 -0
  149. package/lib/typescript/module/provider/BloomProvider.d.ts +1 -1
  150. package/lib/typescript/module/provider/BloomProvider.d.ts.map +1 -1
  151. package/lib/typescript/module/provider/types.d.ts +6 -0
  152. package/lib/typescript/module/provider/types.d.ts.map +1 -1
  153. package/package.json +18 -1
  154. package/src/calendar/shared.ts +5 -12
  155. package/src/date-picker/Calendar.tsx +8 -2
  156. package/src/date-picker/CalendarMonth.tsx +8 -5
  157. package/src/date-picker/DatePicker.tsx +10 -4
  158. package/src/date-picker/DateRangePicker.tsx +14 -13
  159. package/src/date-picker/MeetingScheduler.tsx +17 -11
  160. package/src/date-picker/PickerShell.tsx +6 -5
  161. package/src/date-picker/calendar-grid.ts +16 -23
  162. package/src/date-picker/messages.ts +470 -0
  163. package/src/date-picker/types.ts +28 -24
  164. package/src/index.ts +2 -0
  165. package/src/index.web.ts +2 -0
  166. package/src/locale/context.ts +23 -0
  167. package/src/locale/format-date.ts +28 -0
  168. package/src/locale/index.ts +4 -0
  169. package/src/locale/languages.ts +45 -0
  170. package/src/locale/messages.ts +29 -0
  171. package/src/locale/plural.ts +53 -0
  172. package/src/provider/BloomProvider.tsx +39 -35
  173. package/src/provider/types.ts +6 -0
@@ -14,5 +14,11 @@ export interface BloomProviderProps extends Omit<BloomThemeProviderProps, 'child
14
14
  imageResolver?: ImageResolver;
15
15
  /** `false` disables haptic feedback app-wide (honored by every `useHaptics()` call). */
16
16
  haptics?: boolean;
17
+ /**
18
+ * BCP 47 locale for every Bloom component below — dates, month names and
19
+ * Bloom's own strings (Cancel, Apply, …). A component's own `locale` prop
20
+ * wins. Unset, components follow the runtime's locale.
21
+ */
22
+ locale?: string;
17
23
  }
18
24
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/provider/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAExD,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC;IACnF,QAAQ,EAAE,SAAS,CAAC;IACpB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,wFAAwF;IACxF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/provider/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAC3D,OAAO,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,KAAK,uBAAuB,EAAE,MAAM,UAAU,CAAC;AAExD,MAAM,WAAW,kBAAmB,SAAQ,IAAI,CAAC,uBAAuB,EAAE,UAAU,CAAC;IACnF,QAAQ,EAAE,SAAS,CAAC;IACpB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,mBAAmB,CAAC;IACpC;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,wFAAwF;IACxF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;OAIG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxy.so/bloom",
3
- "version": "4.32.0",
3
+ "version": "4.33.0",
4
4
  "packageManager": "bun@1.3.14",
5
5
  "description": "Bloom UI — Oxy ecosystem component library for React Native + Expo + Web",
6
6
  "main": "lib/commonjs/index.js",
@@ -79,6 +79,20 @@
79
79
  "default": "./lib/commonjs/image-resolver/index.js"
80
80
  }
81
81
  },
82
+ "./locale": {
83
+ "react-native": {
84
+ "types": "./lib/typescript/module/locale/index.d.ts",
85
+ "default": "./src/locale/index.ts"
86
+ },
87
+ "import": {
88
+ "types": "./lib/typescript/module/locale/index.d.ts",
89
+ "default": "./lib/module/locale/index.js"
90
+ },
91
+ "require": {
92
+ "types": "./lib/typescript/commonjs/locale/index.d.ts",
93
+ "default": "./lib/commonjs/locale/index.js"
94
+ }
95
+ },
82
96
  "./image-aspect-ratio-cache": {
83
97
  "react-native": {
84
98
  "types": "./lib/typescript/module/image-aspect-ratio-cache/index.d.ts",
@@ -3452,6 +3466,9 @@
3452
3466
  "image-resolver": [
3453
3467
  "./lib/typescript/module/image-resolver/index.d.ts"
3454
3468
  ],
3469
+ "locale": [
3470
+ "./lib/typescript/module/locale/index.d.ts"
3471
+ ],
3455
3472
  "image-aspect-ratio-cache": [
3456
3473
  "./lib/typescript/module/image-aspect-ratio-cache/index.d.ts"
3457
3474
  ],
@@ -3,6 +3,7 @@ import { Platform, useWindowDimensions } from 'react-native';
3
3
 
4
4
  import { addDays, buildMonthGrid, isSameMonth, startOfDay } from '../date-picker/calendar-grid';
5
5
  import type { CalendarViewEvent } from './types';
6
+ import { formatGregorian } from '../locale/format-date';
6
7
 
7
8
  export const IS_WEB = Platform.OS === 'web';
8
9
 
@@ -95,18 +96,10 @@ export function currentGmtLabel(now: Date = new Date()): string {
95
96
  return `GMT${offset >= 0 ? '+' : ''}${offset}`;
96
97
  }
97
98
 
98
- function format(date: Date, locale: string | undefined, options: Intl.DateTimeFormatOptions): string | null {
99
- try {
100
- return new Intl.DateTimeFormat(locale, options).format(date);
101
- } catch {
102
- return null;
103
- }
104
- }
105
-
106
99
  /** `Tue, Aug 11` — the details panel's date line. */
107
100
  export function formatEventDate(date: Date, locale?: string): string {
108
101
  return (
109
- format(date, locale, {
102
+ formatGregorian(date, locale, {
110
103
  weekday: 'short',
111
104
  month: 'short',
112
105
  day: 'numeric',
@@ -116,7 +109,7 @@ export function formatEventDate(date: Date, locale?: string): string {
116
109
 
117
110
  /** `Aug` — the month switcher's label below `sm`. */
118
111
  export function formatShortMonth(date: Date, locale?: string): string {
119
- return format(date, locale, { month: 'short' }) ?? String(date.getMonth() + 1);
112
+ return formatGregorian(date, locale, { month: 'short' }) ?? String(date.getMonth() + 1);
120
113
  }
121
114
 
122
115
  /** `Sun` … `Sat` — the month grid's weekday pills. */
@@ -126,8 +119,8 @@ export function weekdayShortLabels(locale?: string): { short: string; long: stri
126
119
  return fallback.map((name, index) => {
127
120
  const date = addDays(sunday, index);
128
121
  return {
129
- short: format(date, locale, { weekday: 'short' }) ?? name,
130
- long: format(date, locale, { weekday: 'long' }) ?? name,
122
+ short: formatGregorian(date, locale, { weekday: 'short' }) ?? name,
123
+ long: formatGregorian(date, locale, { weekday: 'long' }) ?? name,
131
124
  };
132
125
  });
133
126
  }
@@ -2,6 +2,7 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
2
2
  import { View, type StyleProp, type ViewStyle } from 'react-native';
3
3
 
4
4
  import { useControllableState } from '../hooks/use-controllable-state';
5
+ import { useMessages } from '../locale/messages';
5
6
  import { adoptStyleSheet } from '../styles/adopt-style-sheet';
6
7
  import { useTheme } from '../theme/use-theme';
7
8
  import {
@@ -21,6 +22,7 @@ import {
21
22
  type DayCellState,
22
23
  } from './calendar-grid';
23
24
  import { CALENDAR_CSS, CalendarMonth, dayKey } from './CalendarMonth';
25
+ import { DATE_PICKER_MESSAGES } from './messages';
24
26
  import { resolveCalendarPalette } from './palette';
25
27
  import type {
26
28
  CalendarConstraintProps,
@@ -73,13 +75,16 @@ function CalendarFrame({
73
75
  maxDate,
74
76
  isDateUnavailable,
75
77
  weekStartsOn = 0,
76
- locale,
78
+ locale: localeProp,
77
79
  accessibilityLabel,
78
80
  labels,
79
81
  style,
80
82
  testID,
81
83
  }: CalendarFrameProps) {
82
84
  const theme = useTheme();
85
+ const { locale, messages } = useMessages(DATE_PICKER_MESSAGES, localeProp);
86
+ const previousLabel = labels?.previousMonth ?? messages.previousMonth;
87
+ const nextLabel = labels?.nextMonth ?? messages.nextMonth;
83
88
  const palette = useMemo(() => resolveCalendarPalette(theme), [theme]);
84
89
  const [firstMonth, setFirstMonth] = useState(() => startOfMonth(initialDate));
85
90
  const [focusedDate, setFocusedDate] = useState(() => startOfDay(initialDate));
@@ -202,7 +207,8 @@ function CalendarFrame({
202
207
  onKeyDown={onKeyDown}
203
208
  registerNode={registerNode}
204
209
  accessibilityLabel={accessibilityLabel}
205
- labels={labels}
210
+ previousLabel={previousLabel}
211
+ nextLabel={nextLabel}
206
212
  testID={testID ? `${testID}-month-${index}` : undefined}
207
213
  />
208
214
  ))}
@@ -14,7 +14,7 @@ import {
14
14
  type DayCellState,
15
15
  } from './calendar-grid';
16
16
  import type { CalendarPalette } from './palette';
17
- import type { CalendarLabels, WeekStart } from './types';
17
+ import type { WeekStart } from './types';
18
18
 
19
19
  /**
20
20
  * One month panel: header, weekday row and day grid.
@@ -281,7 +281,9 @@ export interface CalendarMonthProps {
281
281
  onKeyDown: (key: string, preventDefault: () => void) => void;
282
282
  registerNode: (key: string, node: View | null) => void;
283
283
  accessibilityLabel?: string;
284
- labels?: CalendarLabels;
284
+ /** The chevrons' names, already localised by the calendar. */
285
+ previousLabel: string;
286
+ nextLabel: string;
285
287
  testID?: string;
286
288
  }
287
289
 
@@ -309,7 +311,8 @@ export function CalendarMonth({
309
311
  onKeyDown,
310
312
  registerNode,
311
313
  accessibilityLabel,
312
- labels,
314
+ previousLabel,
315
+ nextLabel,
313
316
  testID,
314
317
  }: CalendarMonthProps) {
315
318
  const title = formatMonthTitle(month, locale);
@@ -341,7 +344,7 @@ export function CalendarMonth({
341
344
  {onPrevious ? (
342
345
  <NavButton
343
346
  direction="left"
344
- label={labels?.previousMonth ?? 'Previous month'}
347
+ label={previousLabel}
345
348
  onPress={onPrevious}
346
349
  disabled={previousDisabled}
347
350
  palette={palette}
@@ -358,7 +361,7 @@ export function CalendarMonth({
358
361
  {onNext ? (
359
362
  <NavButton
360
363
  direction="right"
361
- label={labels?.nextMonth ?? 'Next month'}
364
+ label={nextLabel}
362
365
  onPress={onNext}
363
366
  disabled={nextDisabled}
364
367
  palette={palette}
@@ -2,9 +2,11 @@ import React, { useState } from 'react';
2
2
  import { View } from 'react-native';
3
3
 
4
4
  import { useControllableState } from '../hooks/use-controllable-state';
5
+ import { useMessages } from '../locale/messages';
5
6
  import { Calendar } from './Calendar';
6
7
  import { formatTriggerDate } from './calendar-grid';
7
8
  import { DateChipInput } from './DateChipInput';
9
+ import { DATE_PICKER_MESSAGES } from './messages';
8
10
  import { PickerActions, PickerShell, usePickerPalette } from './PickerShell';
9
11
  import { SummaryPresence } from './SummaryPresence';
10
12
  import type { DatePickerProps } from './types';
@@ -27,8 +29,8 @@ export function DatePicker({
27
29
  value,
28
30
  defaultValue = null,
29
31
  onChange,
30
- placeholder = 'Select date',
31
- accessibilityLabel = 'Date',
32
+ placeholder: placeholderProp,
33
+ accessibilityLabel: accessibilityLabelProp,
32
34
  labels,
33
35
  disabled,
34
36
  open: openProp,
@@ -38,11 +40,14 @@ export function DatePicker({
38
40
  maxDate,
39
41
  isDateUnavailable,
40
42
  weekStartsOn,
41
- locale,
43
+ locale: localeProp,
42
44
  style,
43
45
  testID,
44
46
  }: DatePickerProps) {
45
47
  const palette = usePickerPalette();
48
+ const { locale, messages } = useMessages(DATE_PICKER_MESSAGES, localeProp);
49
+ const placeholder = placeholderProp ?? messages.datePlaceholder;
50
+ const accessibilityLabel = accessibilityLabelProp ?? messages.dateLabel;
46
51
  const [committed, setCommitted] = useControllableState<Date | null>({
47
52
  value,
48
53
  defaultValue,
@@ -128,7 +133,8 @@ export function DatePicker({
128
133
  setOpenState(false);
129
134
  }}
130
135
  applyDisabled={!pending}
131
- labels={labels}
136
+ cancelLabel={labels?.cancel ?? messages.cancel}
137
+ applyLabel={labels?.apply ?? messages.apply}
132
138
  testID={testID}
133
139
  />
134
140
  </View>
@@ -2,6 +2,7 @@ import React, { useMemo, useState } from 'react';
2
2
  import { Platform, Pressable, View, useWindowDimensions, type TextStyle } from 'react-native';
3
3
 
4
4
  import { useControllableState } from '../hooks/use-controllable-state';
5
+ import { useMessages } from '../locale/messages';
5
6
  import type { WebCssStyle } from '../styles/web-view-style';
6
7
  import { Text, TYPE_SCALE } from '../typography';
7
8
  import {
@@ -13,6 +14,7 @@ import {
13
14
  import { RangeCalendar } from './Calendar';
14
15
  import { MONTH_PANEL_WIDTH } from './CalendarMonth';
15
16
  import { DateChipInput } from './DateChipInput';
17
+ import { DATE_PICKER_MESSAGES } from './messages';
16
18
  import type { CalendarPalette } from './palette';
17
19
  import { PickerActions, PickerShell, usePickerPalette } from './PickerShell';
18
20
  import { SummaryPresence } from './SummaryPresence';
@@ -36,11 +38,6 @@ import type { DateRange, DateRangePickerProps } from './types';
36
38
  /** 16 + 118 + 12 + two panels + 8 between them + 8 — the popup width. */
37
39
  const DUAL_MONTH_WIDTH = 16 + 118 + 12 + MONTH_PANEL_WIDTH * 2 + 8 + 8;
38
40
 
39
- /** The pill's English default; `labels.daysSelected` takes the app's own plural rules. */
40
- function daysSelected(days: number): string {
41
- return `${days} day${days === 1 ? '' : 's'} selected`;
42
- }
43
-
44
41
  /** `text-body-medium`, in Inter. */
45
42
  const BODY: TextStyle = TYPE_SCALE['body-medium'];
46
43
 
@@ -90,8 +87,8 @@ export function DateRangePicker({
90
87
  value,
91
88
  defaultValue = null,
92
89
  onChange,
93
- placeholder = 'Select date range',
94
- accessibilityLabel = 'Date range',
90
+ placeholder: placeholderProp,
91
+ accessibilityLabel: accessibilityLabelProp,
95
92
  quickSelect = true,
96
93
  labels,
97
94
  disabled,
@@ -102,11 +99,14 @@ export function DateRangePicker({
102
99
  maxDate,
103
100
  isDateUnavailable,
104
101
  weekStartsOn,
105
- locale,
102
+ locale: localeProp,
106
103
  style,
107
104
  testID,
108
105
  }: DateRangePickerProps) {
109
106
  const palette = usePickerPalette();
107
+ const { locale, messages } = useMessages(DATE_PICKER_MESSAGES, localeProp);
108
+ const placeholder = placeholderProp ?? messages.rangePlaceholder;
109
+ const accessibilityLabel = accessibilityLabelProp ?? messages.rangeLabel;
110
110
  const { width: windowWidth } = useWindowDimensions();
111
111
  const [committed, setCommitted] = useControllableState<DateRange | null>({
112
112
  value,
@@ -161,7 +161,7 @@ export function DateRangePicker({
161
161
  {presets.map((preset) => (
162
162
  <PresetRow
163
163
  key={preset.key}
164
- label={labels?.presets?.[preset.key] ?? preset.label}
164
+ label={labels?.presets?.[preset.key] ?? messages.presets[preset.key]}
165
165
  active={isSameRange(pending, preset.range)}
166
166
  onPress={() => choose(preset.range)}
167
167
  palette={palette}
@@ -213,7 +213,7 @@ export function DateRangePicker({
213
213
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 5 }}>
214
214
  <DateChipInput
215
215
  date={pending.start}
216
- label={labels?.startDate ?? 'Start date'}
216
+ label={labels?.startDate ?? messages.startDate}
217
217
  palette={palette}
218
218
  onCommit={(start) =>
219
219
  setPending({ start, end: start > pending.end ? start : pending.end })
@@ -222,7 +222,7 @@ export function DateRangePicker({
222
222
  <Text style={{ ...BODY, color: palette.secondaryText }}>-</Text>
223
223
  <DateChipInput
224
224
  date={pending.end}
225
- label={labels?.endDate ?? 'End date'}
225
+ label={labels?.endDate ?? messages.endDate}
226
226
  palette={palette}
227
227
  onCommit={(end) =>
228
228
  setPending({ start: end < pending.start ? end : pending.start, end })
@@ -241,7 +241,7 @@ export function DateRangePicker({
241
241
  }}
242
242
  >
243
243
  <Text style={{ ...BODY, color: palette.secondaryText }}>
244
- {(labels?.daysSelected ?? daysSelected)(days)}
244
+ {(labels?.daysSelected ?? messages.daysSelected)(days)}
245
245
  </Text>
246
246
  </View>
247
247
  ) : null}
@@ -259,7 +259,8 @@ export function DateRangePicker({
259
259
  setOpenState(false);
260
260
  }}
261
261
  applyDisabled={!pending}
262
- labels={labels}
262
+ cancelLabel={labels?.cancel ?? messages.cancel}
263
+ applyLabel={labels?.apply ?? messages.apply}
263
264
  testID={testID}
264
265
  />
265
266
  </View>
@@ -16,11 +16,14 @@ import { useControllableState } from '../hooks/use-controllable-state';
16
16
  import { RiFlagLine } from '../icons/remix/RiFlagLine';
17
17
  import { RiGlobalLine } from '../icons/remix/RiGlobalLine';
18
18
  import { RiVideoLine } from '../icons/remix/RiVideoLine';
19
+ import { formatGregorian } from '../locale/format-date';
20
+ import { useMessages } from '../locale/messages';
19
21
  import type { WebCssStyle } from '../styles/web-view-style';
20
22
  import { useTheme } from '../theme/use-theme';
21
23
  import { Text, TYPE_SCALE } from '../typography';
22
24
  import { Calendar } from './Calendar';
23
25
  import { formatTriggerDate, today } from './calendar-grid';
26
+ import { DATE_PICKER_MESSAGES } from './messages';
24
27
  import type { CalendarPalette } from './palette';
25
28
  import { PickerShell, usePickerPalette } from './PickerShell';
26
29
  import type {
@@ -95,11 +98,11 @@ function formatTime(time: string, format: MeetingSchedulerHourFormat): string {
95
98
  }
96
99
 
97
100
  function weekdayShort(date: Date, locale?: string): string {
98
- try {
99
- return new Intl.DateTimeFormat(locale, { weekday: 'short' }).format(date).slice(0, 2);
100
- } catch {
101
- return ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'][date.getDay()] ?? '';
102
- }
101
+ return (
102
+ formatGregorian(date, locale, { weekday: 'short' })?.slice(0, 2) ??
103
+ ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'][date.getDay()] ??
104
+ ''
105
+ );
103
106
  }
104
107
 
105
108
  // ---------------------------------------------------------------------------
@@ -352,8 +355,8 @@ export function MeetingScheduler({
352
355
  defaultValue,
353
356
  onChange,
354
357
  defaultHourFormat = '24h',
355
- triggerLabel = 'Schedule a meeting',
356
- accessibilityLabel = 'Schedule meeting',
358
+ triggerLabel: triggerLabelProp,
359
+ accessibilityLabel: accessibilityLabelProp,
357
360
  labels,
358
361
  disabled,
359
362
  open: openProp,
@@ -363,11 +366,14 @@ export function MeetingScheduler({
363
366
  maxDate,
364
367
  isDateUnavailable,
365
368
  weekStartsOn,
366
- locale,
369
+ locale: localeProp,
367
370
  style,
368
371
  testID,
369
372
  }: MeetingSchedulerProps) {
370
373
  const theme = useTheme();
374
+ const { locale, messages } = useMessages(DATE_PICKER_MESSAGES, localeProp);
375
+ const triggerLabel = triggerLabelProp ?? messages.meetingTrigger;
376
+ const accessibilityLabel = accessibilityLabelProp ?? messages.meetingLabel;
371
377
  const palette = usePickerPalette();
372
378
  const { width: windowWidth } = useWindowDimensions();
373
379
  const slots = useMemo(() => timeSlots ?? defaultTimeSlots(), [timeSlots]);
@@ -397,8 +403,8 @@ export function MeetingScheduler({
397
403
 
398
404
  const wide = windowWidth >= WIDE_WIDTH + 32;
399
405
  const shadow2xs = theme.isDark ? SHADOW_2XS.dark : SHADOW_2XS.light;
400
- const sendLabel = labels?.send ?? 'Send meeting';
401
- const durationText = (labels?.duration ?? ((n: number) => `${n} minutes`))(meeting.durationMinutes);
406
+ const sendLabel = labels?.send ?? messages.send;
407
+ const durationText = (labels?.duration ?? messages.duration)(meeting.durationMinutes);
402
408
  const chevron = useMemo(() => ({ open, color: palette.secondaryText }), [open, palette.secondaryText]);
403
409
 
404
410
  const hostColumn = (
@@ -505,7 +511,7 @@ export function MeetingScheduler({
505
511
  {formatTriggerDate(pending.date, locale)}
506
512
  </Text>
507
513
  <Text style={[BODY_MEDIUM, { color: palette.secondaryText }]}>
508
- {pending.time ?? labels?.selectTime ?? 'Select a time'}
514
+ {pending.time ?? labels?.selectTime ?? messages.selectTime}
509
515
  </Text>
510
516
  </>
511
517
  ) : null}
@@ -7,7 +7,6 @@ import { RiCalendarLine as CalendarIcon } from '../icons/remix/RiCalendarLine';
7
7
  import { Popover, PopoverContent, PopoverTrigger } from '../popover';
8
8
  import { useTheme } from '../theme/use-theme';
9
9
  import { resolveCalendarPalette, type CalendarPalette } from './palette';
10
- import type { DatePickerLabels } from './types';
11
10
 
12
11
  /**
13
12
  * The chrome both pickers share (`date-picker/shared.tsx`).
@@ -100,22 +99,24 @@ export function PickerActions({
100
99
  onCancel,
101
100
  onApply,
102
101
  applyDisabled,
103
- labels,
102
+ cancelLabel,
103
+ applyLabel,
104
104
  testID,
105
105
  }: {
106
106
  onCancel: () => void;
107
107
  onApply: () => void;
108
108
  applyDisabled: boolean;
109
- labels?: DatePickerLabels;
109
+ cancelLabel: string;
110
+ applyLabel: string;
110
111
  testID?: string;
111
112
  }) {
112
113
  return (
113
114
  <View style={{ flexDirection: 'row', alignItems: 'center', gap: 10 }}>
114
115
  <Button onPress={onCancel} testID={testID ? `${testID}-cancel` : undefined} appearance="subtle" tone="neutral">
115
- {labels?.cancel ?? 'Cancel'}
116
+ {cancelLabel}
116
117
  </Button>
117
118
  <Button onPress={onApply} disabled={applyDisabled} testID={testID ? `${testID}-apply` : undefined}>
118
- {labels?.apply ?? 'Apply'}
119
+ {applyLabel}
119
120
  </Button>
120
121
  </View>
121
122
  );
@@ -11,6 +11,7 @@
11
11
  */
12
12
 
13
13
  import type { DateRange, DateRangePreset, WeekStart } from './types';
14
+ import { formatGregorian } from '../locale/format-date';
14
15
 
15
16
  const MS_PER_DAY = 86_400_000;
16
17
 
@@ -130,37 +131,29 @@ export function weekdayLabels(
130
131
  const sunday = new Date(2023, 0, 1);
131
132
  return Array.from({ length: 7 }, (_, column) => {
132
133
  const date = addDays(sunday, (weekStartsOn + column) % 7);
133
- const long = format(date, locale, { weekday: 'long' }) ?? FALLBACK_WEEKDAYS[date.getDay()]!;
134
- const short = format(date, locale, { weekday: 'short' }) ?? long;
134
+ const long = formatGregorian(date, locale, { weekday: 'long' }) ?? FALLBACK_WEEKDAYS[date.getDay()]!;
135
+ const short = formatGregorian(date, locale, { weekday: 'short' }) ?? long;
135
136
  return { short: short.slice(0, 2), long };
136
137
  });
137
138
  }
138
139
 
139
- function format(date: Date, locale: string | undefined, options: Intl.DateTimeFormatOptions): string | null {
140
- try {
141
- return new Intl.DateTimeFormat(locale, options).format(date);
142
- } catch {
143
- return null;
144
- }
145
- }
146
-
147
140
  /** `September 2026` — the month panel's title. */
148
141
  export function formatMonthTitle(month: Date, locale?: string): string {
149
142
  return (
150
- format(month, locale, { month: 'long', year: 'numeric' }) ??
143
+ formatGregorian(month, locale, { month: 'long', year: 'numeric' }) ??
151
144
  `${month.getMonth() + 1}/${month.getFullYear()}`
152
145
  );
153
146
  }
154
147
 
155
148
  /** `Sep 16, 2026` — the trigger's label. */
156
149
  export function formatTriggerDate(date: Date, locale?: string): string {
157
- return format(date, locale, { month: 'short', day: 'numeric', year: 'numeric' }) ?? formatChipDate(date);
150
+ return formatGregorian(date, locale, { month: 'short', day: 'numeric', year: 'numeric' }) ?? formatChipDate(date);
158
151
  }
159
152
 
160
153
  /** `Wednesday, September 16, 2026` — a day button's accessible name. */
161
154
  export function formatAccessibleDate(date: Date, locale?: string): string {
162
155
  return (
163
- format(date, locale, { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }) ??
156
+ formatGregorian(date, locale, { weekday: 'long', month: 'long', day: 'numeric', year: 'numeric' }) ??
164
157
  formatChipDate(date)
165
158
  );
166
159
  }
@@ -334,22 +327,22 @@ export function clampDate(date: Date, { minDate, maxDate }: DateConstraints): Da
334
327
  return startOfDay(date);
335
328
  }
336
329
 
337
- /** Quick-select presets, relative to `now`. `label` is the English default. */
330
+ /** Quick-select presets, relative to `now`. Their names are `DatePickerMessages.presets`. */
338
331
  export function quickSelectPresets(
339
332
  now: Date = today(),
340
- ): { key: DateRangePreset; label: string; range: DateRange }[] {
333
+ ): { key: DateRangePreset; range: DateRange }[] {
341
334
  const lastMonth = addMonths(now, -1);
342
335
  const lastYear = new Date(now.getFullYear() - 1, now.getMonth(), 1);
343
336
  const yesterday = addDays(now, -1);
344
337
  return [
345
- { key: 'today', label: 'Today', range: { start: now, end: now } },
346
- { key: 'yesterday', label: 'Yesterday', range: { start: yesterday, end: yesterday } },
347
- { key: 'lastWeek', label: 'Last week', range: { start: addDays(now, -7), end: yesterday } },
348
- { key: 'thisMonth', label: 'This month', range: { start: startOfMonth(now), end: endOfMonth(now) } },
349
- { key: 'lastMonth', label: 'Last month', range: { start: startOfMonth(lastMonth), end: endOfMonth(lastMonth) } },
350
- { key: 'thisYear', label: 'This year', range: { start: startOfYear(now), end: endOfYear(now) } },
351
- { key: 'lastYear', label: 'Last year', range: { start: startOfYear(lastYear), end: endOfYear(lastYear) } },
352
- { key: 'allTime', label: 'All time', range: { start: addMonths(now, -120), end: now } },
338
+ { key: 'today', range: { start: now, end: now } },
339
+ { key: 'yesterday', range: { start: yesterday, end: yesterday } },
340
+ { key: 'lastWeek', range: { start: addDays(now, -7), end: yesterday } },
341
+ { key: 'thisMonth', range: { start: startOfMonth(now), end: endOfMonth(now) } },
342
+ { key: 'lastMonth', range: { start: startOfMonth(lastMonth), end: endOfMonth(lastMonth) } },
343
+ { key: 'thisYear', range: { start: startOfYear(now), end: endOfYear(now) } },
344
+ { key: 'lastYear', range: { start: startOfYear(lastYear), end: endOfYear(lastYear) } },
345
+ { key: 'allTime', range: { start: addMonths(now, -120), end: now } },
353
346
  ];
354
347
  }
355
348