@forthtilliath/react-native-kit 0.14.7 → 0.15.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 (103) hide show
  1. package/README.md +37 -17
  2. package/dist/components/index.d.ts +4 -4
  3. package/dist/components/index.d.ts.map +1 -1
  4. package/dist/components/index.js +4 -4
  5. package/dist/components/list/SwipeableRow.d.ts +14 -1
  6. package/dist/components/list/SwipeableRow.d.ts.map +1 -1
  7. package/dist/components/list/SwipeableRow.js +18 -16
  8. package/dist/components/list/Thumbnail.d.ts.map +1 -1
  9. package/dist/components/list/Thumbnail.js +2 -6
  10. package/dist/components/list/UndoToast.d.ts +10 -4
  11. package/dist/components/list/UndoToast.d.ts.map +1 -1
  12. package/dist/components/list/UndoToast.js +11 -8
  13. package/dist/components/list/index.d.ts +4 -4
  14. package/dist/components/list/index.d.ts.map +1 -1
  15. package/dist/components/list/index.js +4 -4
  16. package/dist/components/picker/PhotoPicker.d.ts +7 -1
  17. package/dist/components/picker/PhotoPicker.d.ts.map +1 -1
  18. package/dist/components/picker/PhotoPicker.js +33 -28
  19. package/dist/components/picker/PickerModal.d.ts +7 -1
  20. package/dist/components/picker/PickerModal.d.ts.map +1 -1
  21. package/dist/components/picker/PickerModal.js +8 -28
  22. package/dist/components/picker/PickerModal.styles.d.ts +18 -1
  23. package/dist/components/picker/PickerModal.styles.d.ts.map +1 -1
  24. package/dist/components/picker/PickerModal.styles.js +16 -6
  25. package/dist/components/picker/VoiceSearchButton.d.ts +13 -2
  26. package/dist/components/picker/VoiceSearchButton.d.ts.map +1 -1
  27. package/dist/components/picker/VoiceSearchButton.js +18 -1
  28. package/dist/components/picker/index.d.ts +3 -3
  29. package/dist/components/picker/index.d.ts.map +1 -1
  30. package/dist/components/picker/index.js +3 -3
  31. package/dist/components/settings/AboutSettingsScreen.d.ts +7 -1
  32. package/dist/components/settings/AboutSettingsScreen.d.ts.map +1 -1
  33. package/dist/components/settings/AboutSettingsScreen.js +13 -13
  34. package/dist/components/settings/BackupSettingsScreen.d.ts +9 -59
  35. package/dist/components/settings/BackupSettingsScreen.d.ts.map +1 -1
  36. package/dist/components/settings/BackupSettingsScreen.js +32 -141
  37. package/dist/components/settings/BackupSettingsScreen.styles.d.ts +173 -0
  38. package/dist/components/settings/BackupSettingsScreen.styles.d.ts.map +1 -0
  39. package/dist/components/settings/BackupSettingsScreen.styles.js +98 -0
  40. package/dist/components/settings/ContactSettingsScreen.d.ts +7 -1
  41. package/dist/components/settings/ContactSettingsScreen.d.ts.map +1 -1
  42. package/dist/components/settings/ContactSettingsScreen.js +15 -14
  43. package/dist/components/settings/PrivacySettingsScreen.d.ts +7 -1
  44. package/dist/components/settings/PrivacySettingsScreen.d.ts.map +1 -1
  45. package/dist/components/settings/PrivacySettingsScreen.js +50 -28
  46. package/dist/components/settings/SettingsMenu.d.ts.map +1 -1
  47. package/dist/components/settings/SettingsMenu.js +2 -9
  48. package/dist/components/settings/ThemeSettingsScreen.d.ts +7 -1
  49. package/dist/components/settings/ThemeSettingsScreen.d.ts.map +1 -1
  50. package/dist/components/settings/ThemeSettingsScreen.js +17 -19
  51. package/dist/components/settings/UpdateSettingsScreen.d.ts +11 -2
  52. package/dist/components/settings/UpdateSettingsScreen.d.ts.map +1 -1
  53. package/dist/components/settings/UpdateSettingsScreen.js +7 -31
  54. package/dist/components/settings/UpdateSettingsScreen.styles.d.ts +28 -1
  55. package/dist/components/settings/UpdateSettingsScreen.styles.d.ts.map +1 -1
  56. package/dist/components/settings/UpdateSettingsScreen.styles.js +26 -11
  57. package/dist/components/settings/index.d.ts +7 -7
  58. package/dist/components/settings/index.d.ts.map +1 -1
  59. package/dist/components/settings/index.js +7 -7
  60. package/dist/components/theme/ThemeOptionList.d.ts +7 -1
  61. package/dist/components/theme/ThemeOptionList.d.ts.map +1 -1
  62. package/dist/components/theme/ThemeOptionList.js +6 -17
  63. package/dist/components/theme/ThemeToggle.d.ts +7 -1
  64. package/dist/components/theme/ThemeToggle.d.ts.map +1 -1
  65. package/dist/components/theme/ThemeToggle.js +6 -20
  66. package/dist/components/theme/index.d.ts +2 -2
  67. package/dist/components/theme/index.d.ts.map +1 -1
  68. package/dist/components/theme/index.js +2 -2
  69. package/dist/components/theme/themeLabels.d.ts +14 -0
  70. package/dist/components/theme/themeLabels.d.ts.map +1 -0
  71. package/dist/components/theme/themeLabels.js +5 -0
  72. package/dist/hooks/index.d.ts +3 -3
  73. package/dist/hooks/index.d.ts.map +1 -1
  74. package/dist/hooks/index.js +3 -3
  75. package/dist/hooks/useDebouncedChange.d.ts.map +1 -1
  76. package/dist/hooks/useDebouncedChange.js +6 -7
  77. package/dist/hooks/useSubmitGuard.d.ts.map +1 -1
  78. package/dist/hooks/useSubmitGuard.js +9 -5
  79. package/dist/i18n/KitLocaleProvider.d.ts +19 -0
  80. package/dist/i18n/KitLocaleProvider.d.ts.map +1 -0
  81. package/dist/i18n/KitLocaleProvider.js +16 -0
  82. package/dist/i18n/index.d.ts +3 -0
  83. package/dist/i18n/index.d.ts.map +1 -0
  84. package/dist/i18n/index.js +2 -0
  85. package/dist/i18n/locale.d.ts +14 -0
  86. package/dist/i18n/locale.d.ts.map +1 -0
  87. package/dist/i18n/locale.js +18 -0
  88. package/dist/index.d.ts +5 -2
  89. package/dist/index.d.ts.map +1 -1
  90. package/dist/index.js +5 -2
  91. package/dist/utils/helpers/confirmDestructive.d.ts +6 -0
  92. package/dist/utils/helpers/confirmDestructive.d.ts.map +1 -1
  93. package/dist/utils/helpers/confirmDestructive.js +20 -6
  94. package/dist/utils/helpers/index.d.ts +1 -0
  95. package/dist/utils/helpers/index.d.ts.map +1 -1
  96. package/dist/utils/helpers/index.js +1 -0
  97. package/dist/utils/helpers/mergeSlotStyles.d.ts +20 -0
  98. package/dist/utils/helpers/mergeSlotStyles.d.ts.map +1 -0
  99. package/dist/utils/helpers/mergeSlotStyles.js +26 -0
  100. package/dist/utils/index.d.ts +1 -1
  101. package/dist/utils/index.d.ts.map +1 -1
  102. package/dist/utils/index.js +1 -1
  103. package/package.json +14 -10
package/README.md CHANGED
@@ -18,7 +18,7 @@ Or, from within this monorepo, as a workspace dependency:
18
18
  }
19
19
  ```
20
20
 
21
- `react` and `react-native` are peer dependencies — install them in the consuming app if not already present. `@expo/vector-icons`, `expo-image-picker`, `expo-speech-recognition`, and `react-native-gesture-handler` are also peer dependencies, needed only if you import the components that use them (`Thumbnail`/`PickerModal`/`SwipeableRow`, `PhotoPicker`, `VoiceSearchButton`/`PickerModal`, `SwipeableRow`, respectively).
21
+ `react` and `react-native` are peer dependencies — install them in the consuming app if not already present. `@expo/vector-icons`, `expo-image-picker`, `expo-speech-recognition`, `react-native-gesture-handler` and `react-native-reanimated` are also peer dependencies, needed only if you import the components that use them (`Thumbnail`/`PickerModal`/`SwipeableRow`, `PhotoPicker`, `VoiceSearchButton`/`PickerModal`, `SwipeableRow`, `SwipeableRow`, respectively).
22
22
 
23
23
  ## Usage
24
24
 
@@ -38,6 +38,23 @@ import { PhotoPicker, useSubmitGuard } from "@forthtilliath/react-native-kit";
38
38
 
39
39
  **Avoid the barrel under Jest (or any other CommonJS `require` consumer).** `export * from` re-exports are evaluated eagerly on `require()` — unlike Metro's ESM bundling, there's no tree-shaking to skip the unused ones. Requiring the barrel from _any_ file, even one that only wants a framework-agnostic util like `getMostRecentIds`, pulls in every component's module graph, including native-module imports (`expo-speech-recognition` via `VoiceSearchButton`/`PickerModal`) that don't exist in a Jest environment — this throws `Cannot find native module '...'` at require time, not just at runtime for an unrendered component. Deep imports only ever load the one module you asked for, so they don't have this problem in any environment.
40
40
 
41
+ ### Languages (`locale`)
42
+
43
+ Every component with built-in copy (labels, hints, accessibility labels, default sections…) ships it in **French** (default) and **English**. Pick the language:
44
+
45
+ - per component, with a `locale` prop: `<UndoToast locale="en" … />`;
46
+ - or once for a whole tree — handy for a bilingual app — with `KitLocaleProvider`:
47
+
48
+ ```tsx
49
+ import { KitLocaleProvider } from "@forthtilliath/react-native-kit/i18n/KitLocaleProvider";
50
+
51
+ <KitLocaleProvider locale={userLanguage /* "fr" | "en" */}>
52
+ <App />
53
+ </KitLocaleProvider>;
54
+ ```
55
+
56
+ A component's own `locale` prop wins over the provider, and its `labels` prop still overrides individual strings on top of either. The locale also drives `VoiceSearchButton`'s speech-recognition language (`fr-FR` / `en-US`) and `UpdateSettingsScreen`'s date format, unless `lang` / `dateLocale` are passed. `confirmDestructive`, being a plain function, can't read the provider: pass it `{ locale }`.
57
+
41
58
  ### `<Thumbnail photoUri={...} placeholderIcon="..." />`
42
59
 
43
60
  List-row thumbnail: the photo if there is one, otherwise a placeholder icon.
@@ -54,7 +71,7 @@ import { Thumbnail } from "@forthtilliath/react-native-kit/components/list/Thumb
54
71
 
55
72
  ### `<SwipeableRow onDelete={...} deleteLabel="...">`
56
73
 
57
- Swipe a list row left to reveal a delete button, on top of a tap to edit it.
74
+ Swipe a list row left to reveal a delete button, on top of a tap to edit it. Built on react-native-gesture-handler's `ReanimatedSwipeable`, so it needs `react-native-reanimated` installed. The text under the trash icon defaults to "Supprimer" ("Delete" in English), overridable with `deleteText`.
58
75
 
59
76
  ```tsx
60
77
  import { SwipeableRow } from "@forthtilliath/react-native-kit/components/list/SwipeableRow";
@@ -119,12 +136,13 @@ import { UndoToast } from "@forthtilliath/react-native-kit/components/list/UndoT
119
136
  }
120
137
  ```
121
138
 
122
- | Prop | Type | Default | Notes |
123
- | ------------- | ----------------- | -------- | ----------------------------------------------------------------------------------- |
124
- | `message` | `string` | — | Past tense; single line, truncated with an ellipsis. |
125
- | `actionLabel` | `string` | `"Undo"` | The tappable label on the right. |
126
- | `onAction` | `() => void` | — | Reverse the change here. The host then hides the toast. |
127
- | `styles` | `UndoToastStyles` | — | Per-slot overrides (`toast` / `message` / `action`), each merged after its default. |
139
+ | Prop | Type | Default | Notes |
140
+ | ------------- | ----------------- | --------------------------------- | ----------------------------------------------------------------------------------- |
141
+ | `message` | `string` | — | Past tense; single line, truncated with an ellipsis. |
142
+ | `actionLabel` | `string` | `"Annuler"` (`"Undo"` in English) | The tappable label on the right. |
143
+ | `locale` | `KitLocale` | `"fr"` | Language of the default `actionLabel` (see [Languages](#languages-locale)). |
144
+ | `onAction` | `() => void` | — | Reverse the change here. The host then hides the toast. |
145
+ | `styles` | `UndoToastStyles` | — | Per-slot overrides (`toast` / `message` / `action`), each merged after its default. |
128
146
 
129
147
  Full pattern — commit for good after 5s, localized label, re-themed:
130
148
 
@@ -171,9 +189,11 @@ Microphone button to dictate a search instead of typing it. Safe to mount more t
171
189
  ```tsx
172
190
  import { VoiceSearchButton } from "@forthtilliath/react-native-kit/components/picker/VoiceSearchButton";
173
191
 
174
- <VoiceSearchButton onResult={setQuery} lang="en-US" />;
192
+ <VoiceSearchButton onResult={setQuery} />;
175
193
  ```
176
194
 
195
+ Recognizes French by default (`fr-FR`, or `en-US` with `locale="en"`); pass `lang` for any other BCP 47 language.
196
+
177
197
  ### `<PhotoPicker photoUri={...} onChange={...} savePhoto={...} photoLabel="..." />`
178
198
 
179
199
  Photo picker (camera or library) with a preview and a remove link. `savePhoto` is injected rather than hard-coded, so where/how the picked image gets persisted is entirely up to the caller.
@@ -220,7 +240,7 @@ import {
220
240
 
221
241
  Groups results into sections via each item's `group` (e.g. food groups, ingredients vs recipes) — ignored while searching, where the best global matches are shown instead. Pass `filterItems` for custom ranking (e.g. `rankByNameMatch` from this same package's `utils/`) instead of the default case-insensitive substring match.
222
242
 
223
- For all 5 components above, styling and (where relevant) copy work the same way as `ChangelogNotes`: an optional `styles` prop (all fields optional, neutral defaults) and, for `PhotoPicker`/`PickerModal`, an optional `labels` prop for the built-in French copy.
243
+ For all 5 components above, styling and (where relevant) copy work the same way as `ChangelogNotes`: an optional `styles` prop (all fields optional, neutral defaults) and, for `PhotoPicker`/`PickerModal`, an optional `labels` prop over the built-in French or English copy (see [Languages](#languages-locale)).
224
244
 
225
245
  ### `useSubmitGuard()`
226
246
 
@@ -257,15 +277,14 @@ useDebouncedChange([settingsData, itemsData], 5 * 60 * 1000, () => {
257
277
 
258
278
  ### `confirmDestructive(title, onConfirm, options?)`
259
279
 
260
- Generic destructive-action confirmation (title + message + Cancel/Confirm), for anything irreversible (delete, reset...). Ships with French defaults (`message`, `cancelLabel`, `confirmLabel` all overridable).
280
+ Generic destructive-action confirmation (title + message + Cancel/Confirm), for anything irreversible (delete, reset...). Ships with French defaults, or English ones with `{ locale: "en" }` (`message`, `cancelLabel`, `confirmLabel` all overridable).
261
281
 
262
282
  ```ts
263
283
  import { confirmDestructive } from "@forthtilliath/react-native-kit/utils/helpers/confirmDestructive";
264
284
 
265
285
  confirmDestructive("Delete this item?", () => deleteItem(id), {
266
- message: "This cannot be undone.",
267
- cancelLabel: "Cancel",
268
- confirmLabel: "Delete",
286
+ locale: "en",
287
+ confirmLabel: "Remove",
269
288
  });
270
289
  ```
271
290
 
@@ -568,11 +587,12 @@ import { PrivacySettingsScreen } from "@forthtilliath/react-native-kit/component
568
587
  />;
569
588
  ```
570
589
 
571
- For all 7 components above, styling works the same way as `ChangelogNotes`: an optional `styles` prop (all fields optional, neutral defaults), and where relevant an optional `labels` prop for the built-in French copy.
590
+ For all 7 components above, styling works the same way as `ChangelogNotes`: an optional `styles` prop (all fields optional, neutral defaults), and where relevant an optional `labels` prop over the built-in French or English copy (see [Languages](#languages-locale)).
572
591
 
573
592
  ### Utils (`utils/helpers/`)
574
593
 
575
- `confirmDestructive` is the only remaining util here — it wraps React Native's `Alert`, so it isn't framework-agnostic (see its own section above).
594
+ - `confirmDestructive` — wraps React Native's `Alert`, so it isn't framework-agnostic (see its own section above).
595
+ - `mergeSlotStyles(defaultStyles, styles)` — merges a component's per-slot `styles` overrides onto its defaults as `[default, override]` style arrays, so overriding one property of a slot keeps the rest of its default. Every component of this package uses it; handy for your own components following the same `styles` convention.
576
596
 
577
597
  The framework-agnostic helpers that used to live here (`getPeriodStartMs`, `getMostRecentIds`, `nextInCycle`, `normalizeForSearch`, `rankByNameMatch`, `escapeCsvField`, `formatCsvNumber`, `escapeHtml`) moved to [`@forthtilliath/ts-kit`](../ts-kit#readme) — no React Native dependency on them, so they're usable from Node/web too. They're still re-exported from this package's root barrel for convenience:
578
598
 
@@ -600,4 +620,4 @@ Built to `dist/` (see the `exports` field in `package.json`), so run `pnpm run b
600
620
 
601
621
  ### Testing note
602
622
 
603
- `react-native`'s package entry uses Flow syntax that `@babel/parser`'s flow plugin can't parse (`as Cast` casts aren't supported there), so Vitest can never load the real package directly. Tests alias `react-native` to a minimal stub (`src/__mocks__/react-native.tsx`) instead — see that file for details. `react-native-gesture-handler` ships the same kind of Flow syntax, and `@expo/vector-icons` has its own unrelated ESM-resolution issue under Vite/Rollup — both are stubbed the same way (`src/__mocks__/react-native-gesture-handler.tsx`, `src/__mocks__/expo-vector-icons.tsx`). `expo-speech-recognition` and `expo-image-picker` wrap native modules unavailable under Vitest regardless, so they're mocked with plain spies (`src/__mocks__/expo-speech-recognition.tsx`, `src/__mocks__/expo-image-picker.ts`).
623
+ `react-native`'s package entry uses Flow syntax that `@babel/parser`'s flow plugin can't parse (`as Cast` casts aren't supported there), so Vitest can never load the real package directly. Tests alias `react-native` to a minimal stub (`src/__mocks__/react-native.tsx`) instead — see that file for details. `react-native-gesture-handler` ships the same kind of Flow syntax, and `@expo/vector-icons` has its own unrelated ESM-resolution issue under Vite/Rollup — both are stubbed the same way (`src/__mocks__/react-native-gesture-handler.tsx`, `src/__mocks__/expo-vector-icons.tsx`). `react-native-gesture-handler/ReanimatedSwipeable` needs Reanimated's native runtime, so it gets its own stub too (`src/__mocks__/react-native-gesture-handler-reanimated-swipeable.tsx`). `expo-speech-recognition` and `expo-image-picker` wrap native modules unavailable under Vitest regardless, so they're mocked with plain spies (`src/__mocks__/expo-speech-recognition.tsx`, `src/__mocks__/expo-image-picker.ts`).
@@ -1,5 +1,5 @@
1
- export * from "./list";
2
- export * from "./picker";
3
- export * from "./settings";
4
- export * from "./theme";
1
+ export * from "./list/index.js";
2
+ export * from "./picker/index.js";
3
+ export * from "./settings/index.js";
4
+ export * from "./theme/index.js";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,kBAAkB,CAAC"}
@@ -1,4 +1,4 @@
1
- export * from "./list";
2
- export * from "./picker";
3
- export * from "./settings";
4
- export * from "./theme";
1
+ export * from "./list/index.js";
2
+ export * from "./picker/index.js";
3
+ export * from "./settings/index.js";
4
+ export * from "./theme/index.js";
@@ -1,5 +1,6 @@
1
1
  import type { ReactNode } from "react";
2
2
  import type { StyleProp, TextStyle, ViewStyle } from "react-native";
3
+ import { type KitLocale } from "../../i18n/locale.js";
3
4
  export interface SwipeableRowStyles {
4
5
  deleteAction?: StyleProp<ViewStyle>;
5
6
  deleteActionText?: StyleProp<TextStyle>;
@@ -8,9 +9,21 @@ export interface SwipeableRowStyles {
8
9
  export interface SwipeableRowProps {
9
10
  children: ReactNode;
10
11
  onDelete: () => void;
12
+ /** Accessibility label of the delete button, e.g. `"Delete « Marie »"`. */
11
13
  deleteLabel: string;
14
+ /** Text under the trash icon. Defaults to "Supprimer" (`"Delete"` with `locale="en"`). */
12
15
  deleteText?: string;
16
+ /**
17
+ * Language of the default `deleteText` — French by default, or the
18
+ * nearest `KitLocaleProvider`'s.
19
+ */
20
+ locale?: KitLocale;
13
21
  styles?: SwipeableRowStyles;
14
22
  }
15
- export declare function SwipeableRow({ children, onDelete, deleteLabel, deleteText, styles, }: SwipeableRowProps): import("react").JSX.Element;
23
+ /**
24
+ * Swipe a list row left to reveal a delete button, on top of a tap to edit it.
25
+ * Built on react-native-gesture-handler's `ReanimatedSwipeable`, so the app
26
+ * needs `react-native-reanimated` (a peer dependency).
27
+ */
28
+ export declare function SwipeableRow({ children, onDelete, deleteLabel, deleteText, locale, styles, }: SwipeableRowProps): import("react").JSX.Element;
16
29
  //# sourceMappingURL=SwipeableRow.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SwipeableRow.d.ts","sourceRoot":"","sources":["../../../src/components/list/SwipeableRow.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAKpE,MAAM,WAAW,kBAAkB;IACjC,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACpC,gBAAgB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,kBAAkB,CAAC;CAC7B;AAiBD,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,UAAqB,EACrB,MAAM,GACP,EAAE,iBAAiB,+BAuCnB"}
1
+ {"version":3,"file":"SwipeableRow.d.ts","sourceRoot":"","sources":["../../../src/components/list/SwipeableRow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAKpE,OAAO,EAAE,KAAK,SAAS,EAAgB,MAAM,sBAAsB,CAAC;AAGpE,MAAM,WAAW,kBAAkB;IACjC,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACpC,gBAAgB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACxC,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,SAAS,CAAC;IACpB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,2EAA2E;IAC3E,WAAW,EAAE,MAAM,CAAC;IACpB,0FAA0F;IAC1F,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,kBAAkB,CAAC;CAC7B;AAqBD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,EAC3B,QAAQ,EACR,QAAQ,EACR,WAAW,EACX,UAAU,EACV,MAAM,EACN,MAAM,GACP,EAAE,iBAAiB,+BAiCnB"}
@@ -1,8 +1,13 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { useRef } from "react";
3
2
  import { Pressable, Text } from "react-native";
4
- import { Swipeable } from "react-native-gesture-handler";
3
+ import ReanimatedSwipeable from "react-native-gesture-handler/ReanimatedSwipeable";
5
4
  import { Ionicons } from "@expo/vector-icons";
5
+ import { useKitLocale } from "../../i18n/locale.js";
6
+ import { mergeSlotStyles } from "../../utils/helpers/mergeSlotStyles.js";
7
+ const defaultDeleteTexts = {
8
+ fr: "Supprimer",
9
+ en: "Delete",
10
+ };
6
11
  const defaultStyles = {
7
12
  deleteAction: {
8
13
  backgroundColor: "#dc2626",
@@ -16,22 +21,19 @@ const defaultStyles = {
16
21
  deleteActionText: { color: "#ffffff", fontSize: 11, fontWeight: "600" },
17
22
  deleteIconColor: "#ffffff",
18
23
  };
19
- // Swipe a list row left to reveal a delete button, on top of a tap to edit it.
20
- export function SwipeableRow({ children, onDelete, deleteLabel, deleteText = "Delete", styles, }) {
21
- // Style fields are merged as arrays (default, then override) so a partial
22
- // override (e.g. just backgroundColor) doesn't drop the default's
23
- // width/borderRadius/marginBottom/gap too.
24
+ /**
25
+ * Swipe a list row left to reveal a delete button, on top of a tap to edit it.
26
+ * Built on react-native-gesture-handler's `ReanimatedSwipeable`, so the app
27
+ * needs `react-native-reanimated` (a peer dependency).
28
+ */
29
+ export function SwipeableRow({ children, onDelete, deleteLabel, deleteText, locale, styles, }) {
30
+ const resolvedLocale = useKitLocale(locale);
24
31
  const merged = {
25
- deleteAction: [defaultStyles.deleteAction, styles?.deleteAction],
26
- deleteActionText: [
27
- defaultStyles.deleteActionText,
28
- styles?.deleteActionText,
29
- ],
32
+ ...mergeSlotStyles(defaultStyles, styles),
30
33
  deleteIconColor: styles?.deleteIconColor ?? defaultStyles.deleteIconColor,
31
34
  };
32
- const swipeableRef = useRef(null);
33
- return (_jsx(Swipeable, { ref: swipeableRef, renderRightActions: () => (_jsxs(Pressable, { style: merged.deleteAction, onPress: () => {
34
- swipeableRef.current?.close();
35
+ return (_jsx(ReanimatedSwipeable, { renderRightActions: (_progress, _translation, swipeable) => (_jsxs(Pressable, { style: merged.deleteAction, onPress: () => {
36
+ swipeable.close();
35
37
  onDelete();
36
- }, accessibilityRole: "button", accessibilityLabel: deleteLabel, children: [_jsx(Ionicons, { name: "trash-outline", size: 22, color: merged.deleteIconColor }), _jsx(Text, { style: merged.deleteActionText, children: deleteText })] })), children: children }));
38
+ }, accessibilityRole: "button", accessibilityLabel: deleteLabel, children: [_jsx(Ionicons, { name: "trash-outline", size: 22, color: merged.deleteIconColor }), _jsx(Text, { style: merged.deleteActionText, children: deleteText ?? defaultDeleteTexts[resolvedLocale] })] })), children: children }));
37
39
  }
@@ -1 +1 @@
1
- {"version":3,"file":"Thumbnail.d.ts","sourceRoot":"","sources":["../../../src/components/list/Thumbnail.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAErE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IAClC,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,iFAAiF;IACjF,eAAe,EAAE,MAAM,OAAO,QAAQ,CAAC,QAAQ,CAAC;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAgBD,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,eAAe,EACf,IAAmB,EACnB,MAAM,GACP,EAAE,cAAc,+BA+BhB"}
1
+ {"version":3,"file":"Thumbnail.d.ts","sourceRoot":"","sources":["../../../src/components/list/Thumbnail.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAErE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAI9C,MAAM,WAAW,eAAe;IAC9B,SAAS,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IAClC,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,iFAAiF;IACjF,eAAe,EAAE,MAAM,OAAO,QAAQ,CAAC,QAAQ,CAAC;IAChD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAgBD,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,eAAe,EACf,IAAmB,EACnB,MAAM,GACP,EAAE,cAAc,+BA0BhB"}
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { Image, View } from "react-native";
3
3
  import { Ionicons } from "@expo/vector-icons";
4
+ import { mergeSlotStyles } from "../../utils/helpers/mergeSlotStyles.js";
4
5
  const DEFAULT_SIZE = 48;
5
6
  const defaultStyles = {
6
7
  thumbnail: { borderRadius: 10, backgroundColor: "#f3f4f6" },
@@ -14,13 +15,8 @@ const defaultStyles = {
14
15
  };
15
16
  // List-row thumbnail (photo, or a placeholder icon when there is none).
16
17
  export function Thumbnail({ photoUri, placeholderIcon, size = DEFAULT_SIZE, styles, }) {
17
- // Style fields are merged as arrays (default, then override) so a partial
18
- // override (e.g. just backgroundColor) doesn't drop the default's
19
- // borderRadius, or the placeholder's alignItems/justifyContent that center
20
- // the icon.
21
18
  const merged = {
22
- thumbnail: [defaultStyles.thumbnail, styles?.thumbnail],
23
- placeholder: [defaultStyles.placeholder, styles?.placeholder],
19
+ ...mergeSlotStyles(defaultStyles, styles),
24
20
  iconColor: styles?.iconColor ?? defaultStyles.iconColor,
25
21
  };
26
22
  const dimensions = { width: size, height: size };
@@ -1,4 +1,5 @@
1
1
  import type { StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ import { type KitLocale } from "../../i18n/locale.js";
2
3
  /**
3
4
  * Per-slot style overrides for {@link UndoToast}. Every field is optional and
4
5
  * is **merged after** the matching default (as a style array), so overriding
@@ -10,7 +11,7 @@ export interface UndoToastStyles {
10
11
  toast?: StyleProp<ViewStyle>;
11
12
  /** The message text on the left. Truncated to a single line. */
12
13
  message?: StyleProp<TextStyle>;
13
- /** The action label on the right (the tappable "Undo"). */
14
+ /** The action label on the right (the tappable "Annuler" / "Undo"). */
14
15
  action?: StyleProp<TextStyle>;
15
16
  }
16
17
  export interface UndoToastProps {
@@ -23,9 +24,14 @@ export interface UndoToastProps {
23
24
  /**
24
25
  * Label of the action button.
25
26
  *
26
- * @defaultValue "Undo"
27
+ * @defaultValue "Annuler" (`"Undo"` with `locale="en"`)
27
28
  */
28
29
  actionLabel?: string;
30
+ /**
31
+ * Language of the default `actionLabel` — French by default, or the
32
+ * nearest `KitLocaleProvider`'s.
33
+ */
34
+ locale?: KitLocale;
29
35
  /**
30
36
  * Called when the action button is tapped. Wire this to your reverse-the-
31
37
  * change logic (restore the row, re-insert the item…). The host is
@@ -38,7 +44,7 @@ export interface UndoToastProps {
38
44
  /**
39
45
  * A bottom-of-screen snackbar for **undoing an action that already
40
46
  * happened** — the counterpart to a delete that runs immediately with no
41
- * confirmation dialog (for instance a {@link SwipeToDeleteRow} swipe).
47
+ * confirmation dialog (for instance a {@link SwipeableRow} swipe).
42
48
  *
43
49
  * `UndoToast` is presentational only: it does not time itself out, animate
44
50
  * in/out, or stack. The host owns that — typically a piece of state holding
@@ -96,5 +102,5 @@ export interface UndoToastProps {
96
102
  * )}
97
103
  * ```
98
104
  */
99
- export declare function UndoToast({ message, actionLabel, onAction, styles, }: UndoToastProps): import("react").JSX.Element;
105
+ export declare function UndoToast({ message, actionLabel, locale, onAction, styles, }: UndoToastProps): import("react").JSX.Element;
100
106
  //# sourceMappingURL=UndoToast.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"UndoToast.d.ts","sourceRoot":"","sources":["../../../src/components/list/UndoToast.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGpE;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,gEAAgE;IAChE,OAAO,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/B,2DAA2D;IAC3D,MAAM,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAkCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,WAAoB,EACpB,QAAQ,EACR,MAAM,GACP,EAAE,cAAc,+BAmBhB"}
1
+ {"version":3,"file":"UndoToast.d.ts","sourceRoot":"","sources":["../../../src/components/list/UndoToast.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGpE,OAAO,EAAE,KAAK,SAAS,EAAgB,MAAM,sBAAsB,CAAC;AAGpE;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,gEAAgE;IAChE,OAAO,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/B,uEAAuE;IACvE,MAAM,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAuCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,WAAW,EACX,MAAM,EACN,QAAQ,EACR,MAAM,GACP,EAAE,cAAc,+BAkBhB"}
@@ -1,5 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { Pressable, Text, View } from "react-native";
3
+ import { useKitLocale } from "../../i18n/locale.js";
4
+ import { mergeSlotStyles } from "../../utils/helpers/mergeSlotStyles.js";
5
+ const defaultActionLabels = {
6
+ fr: "Annuler",
7
+ en: "Undo",
8
+ };
3
9
  const defaultStyles = {
4
10
  toast: {
5
11
  position: "absolute",
@@ -34,7 +40,7 @@ const defaultStyles = {
34
40
  /**
35
41
  * A bottom-of-screen snackbar for **undoing an action that already
36
42
  * happened** — the counterpart to a delete that runs immediately with no
37
- * confirmation dialog (for instance a {@link SwipeToDeleteRow} swipe).
43
+ * confirmation dialog (for instance a {@link SwipeableRow} swipe).
38
44
  *
39
45
  * `UndoToast` is presentational only: it does not time itself out, animate
40
46
  * in/out, or stack. The host owns that — typically a piece of state holding
@@ -92,13 +98,10 @@ const defaultStyles = {
92
98
  * )}
93
99
  * ```
94
100
  */
95
- export function UndoToast({ message, actionLabel = "Undo", onAction, styles, }) {
101
+ export function UndoToast({ message, actionLabel, locale, onAction, styles, }) {
102
+ const resolvedLocale = useKitLocale(locale);
96
103
  // Merge each slot as [default, override] so a partial override doesn't drop
97
104
  // the rest of that slot's default (positioning, shadow, flex…).
98
- const merged = {
99
- toast: [defaultStyles.toast, styles?.toast],
100
- message: [defaultStyles.message, styles?.message],
101
- action: [defaultStyles.action, styles?.action],
102
- };
103
- return (_jsxs(View, { style: merged.toast, children: [_jsx(Text, { style: merged.message, numberOfLines: 1, children: message }), _jsx(Pressable, { onPress: onAction, hitSlop: 8, accessibilityRole: "button", children: _jsx(Text, { style: merged.action, children: actionLabel }) })] }));
105
+ const merged = mergeSlotStyles(defaultStyles, styles);
106
+ return (_jsxs(View, { style: merged.toast, children: [_jsx(Text, { style: merged.message, numberOfLines: 1, children: message }), _jsx(Pressable, { onPress: onAction, hitSlop: 8, accessibilityRole: "button", children: _jsx(Text, { style: merged.action, children: actionLabel ?? defaultActionLabels[resolvedLocale] }) })] }));
104
107
  }
@@ -1,5 +1,5 @@
1
- export * from "./ColorDot";
2
- export * from "./SwipeableRow";
3
- export * from "./Thumbnail";
4
- export * from "./UndoToast";
1
+ export * from "./ColorDot.js";
2
+ export * from "./SwipeableRow.js";
3
+ export * from "./Thumbnail.js";
4
+ export * from "./UndoToast.js";
5
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/list/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/list/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,mBAAmB,CAAC;AAClC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,gBAAgB,CAAC"}
@@ -1,4 +1,4 @@
1
- export * from "./ColorDot";
2
- export * from "./SwipeableRow";
3
- export * from "./Thumbnail";
4
- export * from "./UndoToast";
1
+ export * from "./ColorDot.js";
2
+ export * from "./SwipeableRow.js";
3
+ export * from "./Thumbnail.js";
4
+ export * from "./UndoToast.js";
@@ -1,4 +1,5 @@
1
1
  import type { ImageStyle, StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ import { type KitLocale } from "../../i18n/locale.js";
2
3
  export interface PhotoPickerStyles {
3
4
  photoPicker?: StyleProp<ViewStyle>;
4
5
  photoPreview?: StyleProp<ImageStyle>;
@@ -28,7 +29,12 @@ export interface PhotoPickerProps {
28
29
  /** Used as-is in titles/accessibility labels, e.g. "of the container". */
29
30
  photoLabel: string;
30
31
  labels?: PhotoPickerLabels;
32
+ /**
33
+ * Language of the built-in labels — French by default, or the nearest
34
+ * `KitLocaleProvider`'s. `labels` still overrides individual strings.
35
+ */
36
+ locale?: KitLocale;
31
37
  styles?: PhotoPickerStyles;
32
38
  }
33
- export declare function PhotoPicker({ photoUri, onChange, savePhoto, photoLabel, labels, styles, }: PhotoPickerProps): import("react").JSX.Element;
39
+ export declare function PhotoPicker({ photoUri, onChange, savePhoto, photoLabel, labels, locale, styles, }: PhotoPickerProps): import("react").JSX.Element;
34
40
  //# sourceMappingURL=PhotoPicker.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PhotoPicker.d.ts","sourceRoot":"","sources":["../../../src/components/picker/PhotoPicker.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAKhF,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,YAAY,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IACrC,gBAAgB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACxC,oBAAoB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5C,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5C,sBAAsB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IACxD,qBAAqB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IACvD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACvC,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAkDD,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,UAAU,EACV,MAAM,EACN,MAAM,GACP,EAAE,gBAAgB,+BAoHlB"}
1
+ {"version":3,"file":"PhotoPicker.d.ts","sourceRoot":"","sources":["../../../src/components/picker/PhotoPicker.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAKhF,OAAO,EAAE,KAAK,SAAS,EAAgB,MAAM,sBAAsB,CAAC;AAGpE,MAAM,WAAW,iBAAiB;IAChC,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,YAAY,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IACrC,gBAAgB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACxC,oBAAoB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5C,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IAC5C,sBAAsB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IACxD,qBAAqB,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,MAAM,CAAC;IACvD,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,QAAQ,EAAE,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,KAAK,IAAI,CAAC;IACvC,SAAS,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAClD,0EAA0E;IAC1E,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAuED,wBAAgB,WAAW,CAAC,EAC1B,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,UAAU,EACV,MAAM,EACN,MAAM,EACN,MAAM,GACP,EAAE,gBAAgB,+BAuGlB"}
@@ -2,6 +2,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
2
2
  import { Alert, Image, Pressable, Text, View } from "react-native";
3
3
  import { Ionicons } from "@expo/vector-icons";
4
4
  import * as ImagePicker from "expo-image-picker";
5
+ import { useKitLocale } from "../../i18n/locale.js";
6
+ import { mergeSlotStyles } from "../../utils/helpers/mergeSlotStyles.js";
5
7
  const defaultStyles = {
6
8
  photoPicker: { alignSelf: "center", marginTop: 8 },
7
9
  photoPreview: {
@@ -32,39 +34,42 @@ const defaultStyles = {
32
34
  iconColor: "#6b7280",
33
35
  };
34
36
  const defaultLabels = {
35
- photoTitle: (photoLabel) => `Photo ${photoLabel}`,
36
- editAccessibilityLabel: (photoLabel) => `Photo ${photoLabel}. Modifier.`,
37
- addAccessibilityLabel: (photoLabel) => `Ajouter une photo ${photoLabel}`,
38
- addPlaceholderText: "Ajouter une photo",
39
- cameraPermissionTitle: "Accès à l'appareil photo refusé",
40
- cameraPermissionMessage: "Autorise l'accès dans les réglages du téléphone pour prendre une photo.",
41
- libraryPermissionTitle: "Accès aux photos refusé",
42
- libraryPermissionMessage: "Autorise l'accès dans les réglages du téléphone pour choisir une photo.",
43
- takePhoto: "Prendre une photo",
44
- chooseFromLibrary: "Choisir dans la galerie",
45
- cancel: "Annuler",
46
- removePhoto: "Retirer la photo",
37
+ fr: {
38
+ photoTitle: (photoLabel) => `Photo ${photoLabel}`,
39
+ editAccessibilityLabel: (photoLabel) => `Photo ${photoLabel}. Modifier.`,
40
+ addAccessibilityLabel: (photoLabel) => `Ajouter une photo ${photoLabel}`,
41
+ addPlaceholderText: "Ajouter une photo",
42
+ cameraPermissionTitle: "Accès à l'appareil photo refusé",
43
+ cameraPermissionMessage: "Autorise l'accès dans les réglages du téléphone pour prendre une photo.",
44
+ libraryPermissionTitle: "Accès aux photos refusé",
45
+ libraryPermissionMessage: "Autorise l'accès dans les réglages du téléphone pour choisir une photo.",
46
+ takePhoto: "Prendre une photo",
47
+ chooseFromLibrary: "Choisir dans la galerie",
48
+ cancel: "Annuler",
49
+ removePhoto: "Retirer la photo",
50
+ },
51
+ en: {
52
+ photoTitle: (photoLabel) => `Photo ${photoLabel}`,
53
+ editAccessibilityLabel: (photoLabel) => `Photo ${photoLabel}. Edit.`,
54
+ addAccessibilityLabel: (photoLabel) => `Add a photo ${photoLabel}`,
55
+ addPlaceholderText: "Add a photo",
56
+ cameraPermissionTitle: "Camera access denied",
57
+ cameraPermissionMessage: "Allow access in the phone's settings to take a photo.",
58
+ libraryPermissionTitle: "Photo access denied",
59
+ libraryPermissionMessage: "Allow access in the phone's settings to choose a photo.",
60
+ takePhoto: "Take a photo",
61
+ chooseFromLibrary: "Choose from library",
62
+ cancel: "Cancel",
63
+ removePhoto: "Remove photo",
64
+ },
47
65
  };
48
66
  // Photo picker (camera or library) with a preview.
49
- export function PhotoPicker({ photoUri, onChange, savePhoto, photoLabel, labels, styles, }) {
50
- // Style fields are merged as arrays (default, then override) so a partial
51
- // override only changes the properties it specifies instead of replacing
52
- // the whole default style object.
67
+ export function PhotoPicker({ photoUri, onChange, savePhoto, photoLabel, labels, locale, styles, }) {
53
68
  const mergedStyles = {
54
- photoPicker: [defaultStyles.photoPicker, styles?.photoPicker],
55
- photoPreview: [defaultStyles.photoPreview, styles?.photoPreview],
56
- photoPlaceholder: [
57
- defaultStyles.photoPlaceholder,
58
- styles?.photoPlaceholder,
59
- ],
60
- photoPlaceholderText: [
61
- defaultStyles.photoPlaceholderText,
62
- styles?.photoPlaceholderText,
63
- ],
64
- clearLink: [defaultStyles.clearLink, styles?.clearLink],
69
+ ...mergeSlotStyles(defaultStyles, styles),
65
70
  iconColor: styles?.iconColor ?? defaultStyles.iconColor,
66
71
  };
67
- const t = { ...defaultLabels, ...labels };
72
+ const t = { ...defaultLabels[useKitLocale(locale)], ...labels };
68
73
  async function pickFromCamera() {
69
74
  const permission = await ImagePicker.requestCameraPermissionsAsync();
70
75
  if (!permission.granted) {
@@ -1,3 +1,4 @@
1
+ import { type KitLocale } from "../../i18n/locale.js";
1
2
  import type { PickerModalLabels, PickerModalStyles } from "./PickerModal.styles.js";
2
3
  export type { PickerModalLabels, PickerModalStyles, } from "./PickerModal.styles.js";
3
4
  export interface PickerItem {
@@ -35,7 +36,12 @@ export interface PickerModalProps {
35
36
  */
36
37
  groupOrder?: string[];
37
38
  labels?: PickerModalLabels;
39
+ /**
40
+ * Language of the built-in labels and of voice search — French by default, or the nearest
41
+ * `KitLocaleProvider`'s. `labels` still overrides individual strings.
42
+ */
43
+ locale?: KitLocale;
38
44
  styles?: PickerModalStyles;
39
45
  }
40
- export declare function PickerModal({ visible, title, items, onSelect, onClose, emptyMessage, initialQuery, filterItems, extraActions, groupOrder, labels, styles, }: PickerModalProps): import("react").JSX.Element;
46
+ export declare function PickerModal({ visible, title, items, onSelect, onClose, emptyMessage, initialQuery, filterItems, extraActions, groupOrder, labels, locale, styles, }: PickerModalProps): import("react").JSX.Element;
41
47
  //# sourceMappingURL=PickerModal.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PickerModal.d.ts","sourceRoot":"","sources":["../../../src/components/picker/PickerModal.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,yBAAyB,CAAC;AAMjC,YAAY,EACV,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8FAA8F;IAC9F,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,KAAK,UAAU,EAAE,CAAC;IACnE,qFAAqF;IACrF,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,EAAE,CAAC;IACxD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAID,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,KAAK,EACL,KAAK,EACL,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,UAAU,EACV,MAAM,EACN,MAAM,GACP,EAAE,gBAAgB,+BAwJlB"}
1
+ {"version":3,"file":"PickerModal.d.ts","sourceRoot":"","sources":["../../../src/components/picker/PickerModal.tsx"],"names":[],"mappings":"AAWA,OAAO,EAAE,KAAK,SAAS,EAAgB,MAAM,sBAAsB,CAAC;AAGpE,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,yBAAyB,CAAC;AAMjC,YAAY,EACV,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8FAA8F;IAC9F,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,KAAK,UAAU,EAAE,CAAC;IACnE,qFAAqF;IACrF,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,EAAE,CAAC;IACxD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;OAGG;IACH,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAID,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,KAAK,EACL,KAAK,EACL,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,UAAU,EACV,MAAM,EACN,MAAM,EACN,MAAM,GACP,EAAE,gBAAgB,+BAsIlB"}
@@ -2,45 +2,25 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo, useState } from "react";
3
3
  import { Modal, Pressable, SectionList, Text, TextInput, View, } from "react-native";
4
4
  import { Ionicons } from "@expo/vector-icons";
5
+ import { useKitLocale } from "../../i18n/locale.js";
6
+ import { mergeSlotStyles } from "../../utils/helpers/mergeSlotStyles.js";
5
7
  import { defaultLabels, defaultStyles } from "./PickerModal.styles.js";
6
8
  import { PickerModalRow } from "./PickerModalRow.js";
7
9
  import { usePickerModalSections } from "./usePickerModalSections.js";
8
10
  import { VoiceSearchButton } from "./VoiceSearchButton.js";
9
11
  // Full-screen picker: search (typed or dictated), optional sections, and
10
12
  // "add" actions always visible above the results.
11
- export function PickerModal({ visible, title, items, onSelect, onClose, emptyMessage, initialQuery, filterItems, extraActions, groupOrder, labels, styles, }) {
12
- // Style fields are merged as arrays (default, then override) so a partial
13
- // override only changes the properties it specifies instead of replacing
14
- // the whole default style object (e.g. losing rowThumbnailPlaceholder's
15
- // alignItems/justifyContent by only overriding its backgroundColor).
13
+ export function PickerModal({ visible, title, items, onSelect, onClose, emptyMessage, initialQuery, filterItems, extraActions, groupOrder, labels, locale, styles, }) {
14
+ // Memoized: the rows (and their thumbnails) re-render on every keystroke.
16
15
  const merged = useMemo(() => ({
17
- container: [defaultStyles.container, styles?.container],
18
- header: [defaultStyles.header, styles?.header],
19
- title: [defaultStyles.title, styles?.title],
20
- close: [defaultStyles.close, styles?.close],
21
- searchRow: [defaultStyles.searchRow, styles?.searchRow],
22
- search: [defaultStyles.search, styles?.search],
23
- row: [defaultStyles.row, styles?.row],
24
- rowThumbnail: [defaultStyles.rowThumbnail, styles?.rowThumbnail],
25
- rowThumbnailPlaceholder: [
26
- defaultStyles.rowThumbnailPlaceholder,
27
- styles?.rowThumbnailPlaceholder,
28
- ],
29
- rowLabel: [defaultStyles.rowLabel, styles?.rowLabel],
30
- rowSubtitle: [defaultStyles.rowSubtitle, styles?.rowSubtitle],
31
- empty: [defaultStyles.empty, styles?.empty],
32
- sectionHeader: [defaultStyles.sectionHeader, styles?.sectionHeader],
33
- extraActions: [defaultStyles.extraActions, styles?.extraActions],
34
- extraActionLabel: [
35
- defaultStyles.extraActionLabel,
36
- styles?.extraActionLabel,
37
- ],
16
+ ...mergeSlotStyles(defaultStyles, styles),
38
17
  extraActionIconColor: styles?.extraActionIconColor ?? defaultStyles.extraActionIconColor,
39
18
  rowThumbnailPlaceholderIconColor: styles?.rowThumbnailPlaceholderIconColor ??
40
19
  defaultStyles.rowThumbnailPlaceholderIconColor,
41
20
  placeholderTextColor: styles?.placeholderTextColor ?? defaultStyles.placeholderTextColor,
42
21
  }), [styles]);
43
- const t = useMemo(() => ({ ...defaultLabels, ...labels }), [labels]);
22
+ const resolvedLocale = useKitLocale(locale);
23
+ const t = useMemo(() => ({ ...defaultLabels[resolvedLocale], ...labels }), [resolvedLocale, labels]);
44
24
  const [query, setQuery] = useState(initialQuery ?? "");
45
25
  // Reset the search each time the modal (re-)opens — adjusted during render
46
26
  // rather than in an effect, since it only needs to happen once per
@@ -65,5 +45,5 @@ export function PickerModal({ visible, title, items, onSelect, onClose, emptyMes
65
45
  onSelect(item);
66
46
  setQuery("");
67
47
  }
68
- return (_jsx(Modal, { visible: visible, animationType: "slide", onRequestClose: onClose, children: _jsxs(View, { style: merged.container, children: [_jsxs(View, { style: merged.header, children: [_jsx(Text, { style: merged.title, children: title }), _jsx(Pressable, { onPress: onClose, hitSlop: 12, accessibilityRole: "button", accessibilityLabel: t.close, children: _jsx(Text, { style: merged.close, children: t.close }) })] }), _jsxs(View, { style: merged.searchRow, children: [_jsx(TextInput, { style: merged.search, placeholder: t.searchPlaceholder, placeholderTextColor: merged.placeholderTextColor, value: query, onChangeText: setQuery, autoFocus: true, accessibilityLabel: t.searchAccessibilityLabel }), _jsx(VoiceSearchButton, { onResult: setQuery, accessibilityLabel: t.voiceSearchAccessibilityLabel })] }), _jsx(SectionList, { sections: sections, keyExtractor: (item) => String(item.id), keyboardShouldPersistTaps: "handled", stickySectionHeadersEnabled: false, ListHeaderComponent: extraActions && extraActions.length > 0 ? (_jsx(View, { style: merged.extraActions, children: extraActions.map((action) => (_jsxs(Pressable, { style: merged.row, onPress: action.onPress, accessibilityRole: "button", accessibilityLabel: action.label, children: [_jsx(Ionicons, { name: "add-circle-outline", size: 20, color: merged.extraActionIconColor }), _jsx(Text, { style: [merged.rowLabel, merged.extraActionLabel], children: action.label })] }, action.label))) })) : null, ListEmptyComponent: _jsx(Text, { style: merged.empty, children: emptyMessage ?? t.defaultEmptyMessage }), renderSectionHeader: ({ section: { title: sectionTitle }, }) => sectionTitle ? (_jsx(Text, { style: merged.sectionHeader, children: sectionTitle })) : null, renderItem: ({ item }) => (_jsx(PickerModalRow, { item: item, styles: merged, onSelect: handleSelect })) })] }) }));
48
+ return (_jsx(Modal, { visible: visible, animationType: "slide", onRequestClose: onClose, children: _jsxs(View, { style: merged.container, children: [_jsxs(View, { style: merged.header, children: [_jsx(Text, { style: merged.title, children: title }), _jsx(Pressable, { onPress: onClose, hitSlop: 12, accessibilityRole: "button", accessibilityLabel: t.close, children: _jsx(Text, { style: merged.close, children: t.close }) })] }), _jsxs(View, { style: merged.searchRow, children: [_jsx(TextInput, { style: merged.search, placeholder: t.searchPlaceholder, placeholderTextColor: merged.placeholderTextColor, value: query, onChangeText: setQuery, autoFocus: true, accessibilityLabel: t.searchAccessibilityLabel }), _jsx(VoiceSearchButton, { onResult: setQuery, accessibilityLabel: t.voiceSearchAccessibilityLabel, locale: resolvedLocale })] }), _jsx(SectionList, { sections: sections, keyExtractor: (item) => String(item.id), keyboardShouldPersistTaps: "handled", stickySectionHeadersEnabled: false, ListHeaderComponent: extraActions && extraActions.length > 0 ? (_jsx(View, { style: merged.extraActions, children: extraActions.map((action) => (_jsxs(Pressable, { style: merged.row, onPress: action.onPress, accessibilityRole: "button", accessibilityLabel: action.label, children: [_jsx(Ionicons, { name: "add-circle-outline", size: 20, color: merged.extraActionIconColor }), _jsx(Text, { style: [merged.rowLabel, merged.extraActionLabel], children: action.label })] }, action.label))) })) : null, ListEmptyComponent: _jsx(Text, { style: merged.empty, children: emptyMessage ?? t.defaultEmptyMessage }), renderSectionHeader: ({ section: { title: sectionTitle }, }) => sectionTitle ? (_jsx(Text, { style: merged.sectionHeader, children: sectionTitle })) : null, renderItem: ({ item }) => (_jsx(PickerModalRow, { item: item, styles: merged, onSelect: handleSelect })) })] }) }));
69
49
  }