@forthtilliath/react-native-kit 0.8.0 → 0.10.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 (144) hide show
  1. package/README.md +183 -23
  2. package/dist/components/index.d.ts +6 -0
  3. package/dist/components/index.d.ts.map +1 -0
  4. package/dist/components/index.js +5 -0
  5. package/dist/components/list/SwipeableRow.d.ts.map +1 -0
  6. package/dist/components/list/Thumbnail.d.ts.map +1 -0
  7. package/dist/components/list/index.d.ts +3 -0
  8. package/dist/components/list/index.d.ts.map +1 -0
  9. package/dist/components/list/index.js +2 -0
  10. package/dist/components/picker/PhotoPicker.d.ts.map +1 -0
  11. package/dist/components/picker/PickerModal.d.ts.map +1 -0
  12. package/dist/components/picker/VoiceSearchButton.d.ts.map +1 -0
  13. package/dist/components/picker/index.d.ts +4 -0
  14. package/dist/components/picker/index.d.ts.map +1 -0
  15. package/dist/components/picker/index.js +3 -0
  16. package/dist/components/settings/AboutSettingsScreen.d.ts +24 -0
  17. package/dist/components/settings/AboutSettingsScreen.d.ts.map +1 -0
  18. package/dist/components/settings/AboutSettingsScreen.js +41 -0
  19. package/dist/components/settings/BackupSettingsScreen.d.ts +52 -0
  20. package/dist/components/settings/BackupSettingsScreen.d.ts.map +1 -0
  21. package/dist/components/settings/BackupSettingsScreen.js +120 -0
  22. package/dist/components/settings/ContactSettingsScreen.d.ts +19 -0
  23. package/dist/components/settings/ContactSettingsScreen.d.ts.map +1 -0
  24. package/dist/components/settings/ContactSettingsScreen.js +39 -0
  25. package/dist/components/settings/PrivacySettingsScreen.d.ts +23 -0
  26. package/dist/components/settings/PrivacySettingsScreen.d.ts.map +1 -0
  27. package/dist/components/settings/PrivacySettingsScreen.js +51 -0
  28. package/dist/components/settings/SettingsMenu.d.ts +24 -0
  29. package/dist/components/settings/SettingsMenu.d.ts.map +1 -0
  30. package/dist/components/settings/SettingsMenu.js +40 -0
  31. package/dist/components/settings/ThemeSettingsScreen.d.ts +32 -0
  32. package/dist/components/settings/ThemeSettingsScreen.d.ts.map +1 -0
  33. package/dist/components/settings/ThemeSettingsScreen.js +71 -0
  34. package/dist/components/settings/UpdateSettingsScreen.d.ts +62 -0
  35. package/dist/components/settings/UpdateSettingsScreen.d.ts.map +1 -0
  36. package/dist/components/settings/UpdateSettingsScreen.js +178 -0
  37. package/dist/components/settings/index.d.ts +8 -0
  38. package/dist/components/settings/index.d.ts.map +1 -0
  39. package/dist/components/settings/index.js +7 -0
  40. package/dist/{ThemeOptionList.d.ts → components/theme/ThemeOptionList.d.ts} +1 -1
  41. package/dist/components/theme/ThemeOptionList.d.ts.map +1 -0
  42. package/dist/{ThemeToggle.d.ts → components/theme/ThemeToggle.d.ts} +1 -1
  43. package/dist/components/theme/ThemeToggle.d.ts.map +1 -0
  44. package/dist/components/theme/index.d.ts +3 -0
  45. package/dist/components/theme/index.d.ts.map +1 -0
  46. package/dist/components/theme/index.js +2 -0
  47. package/dist/components/update/ChangelogNotes.d.ts.map +1 -0
  48. package/dist/components/update/UpdateAvailableBanner.d.ts.map +1 -0
  49. package/dist/components/update/index.d.ts +3 -0
  50. package/dist/components/update/index.d.ts.map +1 -0
  51. package/dist/components/update/index.js +2 -0
  52. package/dist/hooks/index.d.ts +5 -0
  53. package/dist/hooks/index.d.ts.map +1 -0
  54. package/dist/hooks/index.js +4 -0
  55. package/dist/hooks/useDebouncedChange.d.ts.map +1 -0
  56. package/dist/hooks/useEffectiveColorScheme.d.ts.map +1 -0
  57. package/dist/hooks/useSubmitGuard.d.ts.map +1 -0
  58. package/dist/hooks/useUpdateCheck.d.ts.map +1 -0
  59. package/dist/index.d.ts +29 -22
  60. package/dist/index.d.ts.map +1 -1
  61. package/dist/index.js +33 -25
  62. package/dist/utils/format/escapeCsvField.d.ts.map +1 -0
  63. package/dist/utils/format/escapeHtml.d.ts.map +1 -0
  64. package/dist/utils/format/formatCsvNumber.d.ts.map +1 -0
  65. package/dist/utils/format/index.d.ts +4 -0
  66. package/dist/utils/format/index.d.ts.map +1 -0
  67. package/dist/utils/format/index.js +3 -0
  68. package/dist/utils/helpers/confirmDestructive.d.ts.map +1 -0
  69. package/dist/utils/helpers/getMostRecentIds.d.ts.map +1 -0
  70. package/dist/utils/helpers/getPeriodStartMs.d.ts.map +1 -0
  71. package/dist/utils/helpers/index.d.ts +7 -0
  72. package/dist/utils/helpers/index.d.ts.map +1 -0
  73. package/dist/utils/helpers/index.js +6 -0
  74. package/dist/utils/helpers/nextInCycle.d.ts.map +1 -0
  75. package/dist/utils/helpers/normalizeForSearch.d.ts.map +1 -0
  76. package/dist/utils/helpers/rankByNameMatch.d.ts.map +1 -0
  77. package/dist/utils/index.d.ts +3 -0
  78. package/dist/utils/index.d.ts.map +1 -0
  79. package/dist/utils/index.js +2 -0
  80. package/package.json +7 -7
  81. package/dist/ChangelogNotes.d.ts.map +0 -1
  82. package/dist/PhotoPicker.d.ts.map +0 -1
  83. package/dist/PickerModal.d.ts.map +0 -1
  84. package/dist/SwipeableRow.d.ts.map +0 -1
  85. package/dist/ThemeOptionList.d.ts.map +0 -1
  86. package/dist/ThemeToggle.d.ts.map +0 -1
  87. package/dist/Thumbnail.d.ts.map +0 -1
  88. package/dist/UpdateAvailableBanner.d.ts.map +0 -1
  89. package/dist/VoiceSearchButton.d.ts.map +0 -1
  90. package/dist/confirmDestructive.d.ts.map +0 -1
  91. package/dist/useDebouncedChange.d.ts.map +0 -1
  92. package/dist/useEffectiveColorScheme.d.ts.map +0 -1
  93. package/dist/useSubmitGuard.d.ts.map +0 -1
  94. package/dist/useUpdateCheck.d.ts.map +0 -1
  95. package/dist/utils/escapeCsvField.d.ts.map +0 -1
  96. package/dist/utils/escapeHtml.d.ts.map +0 -1
  97. package/dist/utils/formatCsvNumber.d.ts.map +0 -1
  98. package/dist/utils/getMostRecentIds.d.ts.map +0 -1
  99. package/dist/utils/getPeriodStartMs.d.ts.map +0 -1
  100. package/dist/utils/nextInCycle.d.ts.map +0 -1
  101. package/dist/utils/normalizeForSearch.d.ts.map +0 -1
  102. package/dist/utils/rankByNameMatch.d.ts.map +0 -1
  103. /package/dist/{SwipeableRow.d.ts → components/list/SwipeableRow.d.ts} +0 -0
  104. /package/dist/{SwipeableRow.js → components/list/SwipeableRow.js} +0 -0
  105. /package/dist/{Thumbnail.d.ts → components/list/Thumbnail.d.ts} +0 -0
  106. /package/dist/{Thumbnail.js → components/list/Thumbnail.js} +0 -0
  107. /package/dist/{PhotoPicker.d.ts → components/picker/PhotoPicker.d.ts} +0 -0
  108. /package/dist/{PhotoPicker.js → components/picker/PhotoPicker.js} +0 -0
  109. /package/dist/{PickerModal.d.ts → components/picker/PickerModal.d.ts} +0 -0
  110. /package/dist/{PickerModal.js → components/picker/PickerModal.js} +0 -0
  111. /package/dist/{VoiceSearchButton.d.ts → components/picker/VoiceSearchButton.d.ts} +0 -0
  112. /package/dist/{VoiceSearchButton.js → components/picker/VoiceSearchButton.js} +0 -0
  113. /package/dist/{ThemeOptionList.js → components/theme/ThemeOptionList.js} +0 -0
  114. /package/dist/{ThemeToggle.js → components/theme/ThemeToggle.js} +0 -0
  115. /package/dist/{ChangelogNotes.d.ts → components/update/ChangelogNotes.d.ts} +0 -0
  116. /package/dist/{ChangelogNotes.js → components/update/ChangelogNotes.js} +0 -0
  117. /package/dist/{UpdateAvailableBanner.d.ts → components/update/UpdateAvailableBanner.d.ts} +0 -0
  118. /package/dist/{UpdateAvailableBanner.js → components/update/UpdateAvailableBanner.js} +0 -0
  119. /package/dist/{useDebouncedChange.d.ts → hooks/useDebouncedChange.d.ts} +0 -0
  120. /package/dist/{useDebouncedChange.js → hooks/useDebouncedChange.js} +0 -0
  121. /package/dist/{useEffectiveColorScheme.d.ts → hooks/useEffectiveColorScheme.d.ts} +0 -0
  122. /package/dist/{useEffectiveColorScheme.js → hooks/useEffectiveColorScheme.js} +0 -0
  123. /package/dist/{useSubmitGuard.d.ts → hooks/useSubmitGuard.d.ts} +0 -0
  124. /package/dist/{useSubmitGuard.js → hooks/useSubmitGuard.js} +0 -0
  125. /package/dist/{useUpdateCheck.d.ts → hooks/useUpdateCheck.d.ts} +0 -0
  126. /package/dist/{useUpdateCheck.js → hooks/useUpdateCheck.js} +0 -0
  127. /package/dist/utils/{escapeCsvField.d.ts → format/escapeCsvField.d.ts} +0 -0
  128. /package/dist/utils/{escapeCsvField.js → format/escapeCsvField.js} +0 -0
  129. /package/dist/utils/{escapeHtml.d.ts → format/escapeHtml.d.ts} +0 -0
  130. /package/dist/utils/{escapeHtml.js → format/escapeHtml.js} +0 -0
  131. /package/dist/utils/{formatCsvNumber.d.ts → format/formatCsvNumber.d.ts} +0 -0
  132. /package/dist/utils/{formatCsvNumber.js → format/formatCsvNumber.js} +0 -0
  133. /package/dist/{confirmDestructive.d.ts → utils/helpers/confirmDestructive.d.ts} +0 -0
  134. /package/dist/{confirmDestructive.js → utils/helpers/confirmDestructive.js} +0 -0
  135. /package/dist/utils/{getMostRecentIds.d.ts → helpers/getMostRecentIds.d.ts} +0 -0
  136. /package/dist/utils/{getMostRecentIds.js → helpers/getMostRecentIds.js} +0 -0
  137. /package/dist/utils/{getPeriodStartMs.d.ts → helpers/getPeriodStartMs.d.ts} +0 -0
  138. /package/dist/utils/{getPeriodStartMs.js → helpers/getPeriodStartMs.js} +0 -0
  139. /package/dist/utils/{nextInCycle.d.ts → helpers/nextInCycle.d.ts} +0 -0
  140. /package/dist/utils/{nextInCycle.js → helpers/nextInCycle.js} +0 -0
  141. /package/dist/utils/{normalizeForSearch.d.ts → helpers/normalizeForSearch.d.ts} +0 -0
  142. /package/dist/utils/{normalizeForSearch.js → helpers/normalizeForSearch.js} +0 -0
  143. /package/dist/utils/{rankByNameMatch.d.ts → helpers/rankByNameMatch.d.ts} +0 -0
  144. /package/dist/utils/{rankByNameMatch.js → helpers/rankByNameMatch.js} +0 -0
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @forthtilliath/react-native-kit
2
2
 
3
- Small React Native building blocks — components, hooks, and framework-agnostic utils — with no opinion on your app's theme, distributed one file per export so consumers only pull in what they use.
3
+ Small React Native building blocks — components, hooks, and framework-agnostic utils — with no opinion on your app's theme, distributed one file per export (grouped under `components/{picker,theme,update,list,settings}/`, `hooks/`, `utils/{format,helpers}/`) so consumers only pull in what they use.
4
4
 
5
5
  ## Install
6
6
 
@@ -25,7 +25,7 @@ Or, from within this monorepo, as a workspace dependency:
25
25
  Each component is its own module — import the file you need directly:
26
26
 
27
27
  ```ts
28
- import { ChangelogNotes } from "@forthtilliath/react-native-kit/ChangelogNotes";
28
+ import { ChangelogNotes } from "@forthtilliath/react-native-kit/components/update/ChangelogNotes";
29
29
  ```
30
30
 
31
31
  This is the recommended way to import: Metro (React Native's bundler) doesn't reliably tree-shake, so pulling from a single deep-import path keeps peer dependencies you don't use (`expo-image-picker`, `expo-speech-recognition`, `react-native-gesture-handler`...) out of your bundle entirely, rather than merely unused.
@@ -88,7 +88,7 @@ Every field of `styles` is optional — pass only the ones you want to override;
88
88
  List-row thumbnail: the photo if there is one, otherwise a placeholder icon.
89
89
 
90
90
  ```tsx
91
- import { Thumbnail } from "@forthtilliath/react-native-kit/Thumbnail";
91
+ import { Thumbnail } from "@forthtilliath/react-native-kit/components/list/Thumbnail";
92
92
 
93
93
  <Thumbnail
94
94
  photoUri={container.photoUri}
@@ -102,7 +102,7 @@ import { Thumbnail } from "@forthtilliath/react-native-kit/Thumbnail";
102
102
  Swipe a list row left to reveal a delete button, on top of a tap to edit it.
103
103
 
104
104
  ```tsx
105
- import { SwipeableRow } from "@forthtilliath/react-native-kit/SwipeableRow";
105
+ import { SwipeableRow } from "@forthtilliath/react-native-kit/components/list/SwipeableRow";
106
106
 
107
107
  <SwipeableRow
108
108
  onDelete={() => remove(item.id)}
@@ -117,7 +117,7 @@ import { SwipeableRow } from "@forthtilliath/react-native-kit/SwipeableRow";
117
117
  Microphone button to dictate a search instead of typing it. Safe to mount more than one at a time (e.g. a name field plus a search picker on the same screen) — only the instance that started listening reacts to its result.
118
118
 
119
119
  ```tsx
120
- import { VoiceSearchButton } from "@forthtilliath/react-native-kit/VoiceSearchButton";
120
+ import { VoiceSearchButton } from "@forthtilliath/react-native-kit/components/picker/VoiceSearchButton";
121
121
 
122
122
  <VoiceSearchButton onResult={setQuery} lang="en-US" />;
123
123
  ```
@@ -127,7 +127,7 @@ import { VoiceSearchButton } from "@forthtilliath/react-native-kit/VoiceSearchBu
127
127
  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.
128
128
 
129
129
  ```tsx
130
- import { PhotoPicker } from "@forthtilliath/react-native-kit/PhotoPicker";
130
+ import { PhotoPicker } from "@forthtilliath/react-native-kit/components/picker/PhotoPicker";
131
131
 
132
132
  <PhotoPicker
133
133
  photoUri={container.photoUri}
@@ -145,7 +145,7 @@ Full-screen picker: search (typed or dictated via `VoiceSearchButton`), optional
145
145
  import {
146
146
  PickerModal,
147
147
  type PickerItem,
148
- } from "@forthtilliath/react-native-kit/PickerModal";
148
+ } from "@forthtilliath/react-native-kit/components/picker/PickerModal";
149
149
 
150
150
  <PickerModal
151
151
  visible={pickerVisible}
@@ -175,7 +175,7 @@ For all 5 components above, styling and (where relevant) copy work the same way
175
175
  Prevents a second call while a first one is still pending — e.g. a double-tap on a "Save" button before it's had time to disable, which would otherwise create duplicate submissions.
176
176
 
177
177
  ```tsx
178
- import { useSubmitGuard } from "@forthtilliath/react-native-kit/useSubmitGuard";
178
+ import { useSubmitGuard } from "@forthtilliath/react-native-kit/hooks/useSubmitGuard";
179
179
 
180
180
  const { isSaving, guard } = useSubmitGuard();
181
181
 
@@ -196,7 +196,7 @@ const { isSaving, guard } = useSubmitGuard();
196
196
  Calls `callback` `delayMs` after the last change among `values`, ignoring renders where any value is still `undefined` (not loaded yet) and the very first render where they're all defined (no trigger on mount). A new change before the delay elapses resets the timer — a real debounce, not a throttle.
197
197
 
198
198
  ```tsx
199
- import { useDebouncedChange } from "@forthtilliath/react-native-kit/useDebouncedChange";
199
+ import { useDebouncedChange } from "@forthtilliath/react-native-kit/hooks/useDebouncedChange";
200
200
 
201
201
  useDebouncedChange([settingsData, itemsData], 5 * 60 * 1000, () => {
202
202
  runAutoBackup();
@@ -208,7 +208,7 @@ useDebouncedChange([settingsData, itemsData], 5 * 60 * 1000, () => {
208
208
  Generic destructive-action confirmation (title + message + Cancel/Confirm), for anything irreversible (delete, reset...). Ships with French defaults (`message`, `cancelLabel`, `confirmLabel` all overridable).
209
209
 
210
210
  ```ts
211
- import { confirmDestructive } from "@forthtilliath/react-native-kit/confirmDestructive";
211
+ import { confirmDestructive } from "@forthtilliath/react-native-kit/utils/helpers/confirmDestructive";
212
212
 
213
213
  confirmDestructive("Delete this item?", () => deleteItem(id), {
214
214
  message: "This cannot be undone.",
@@ -222,7 +222,7 @@ confirmDestructive("Delete this item?", () => deleteItem(id), {
222
222
  Resolves a `"light" | "dark" | "system"` theme preference against the device's color scheme: `"system"` follows the device, `"light"`/`"dark"` override it regardless of what the device is set to.
223
223
 
224
224
  ```tsx
225
- import { useEffectiveColorScheme } from "@forthtilliath/react-native-kit/useEffectiveColorScheme";
225
+ import { useEffectiveColorScheme } from "@forthtilliath/react-native-kit/hooks/useEffectiveColorScheme";
226
226
 
227
227
  const scheme = useEffectiveColorScheme(themePreference); // "light" | "dark"
228
228
  const colors = scheme === "dark" ? darkColors : lightColors;
@@ -233,7 +233,7 @@ const colors = scheme === "dark" ? darkColors : lightColors;
233
233
  3-way segmented control for a light/dark/system theme preference.
234
234
 
235
235
  ```tsx
236
- import { ThemeToggle } from "@forthtilliath/react-native-kit/ThemeToggle";
236
+ import { ThemeToggle } from "@forthtilliath/react-native-kit/components/theme/ThemeToggle";
237
237
 
238
238
  <ThemeToggle value={themePreference} onChange={setThemePreference} />;
239
239
  ```
@@ -243,7 +243,7 @@ import { ThemeToggle } from "@forthtilliath/react-native-kit/ThemeToggle";
243
243
  Same light/dark/system data contract as `ThemeToggle`, but rendered as a list of full-width rows (icon + label + checkmark on the active one) instead of a segmented control.
244
244
 
245
245
  ```tsx
246
- import { ThemeOptionList } from "@forthtilliath/react-native-kit/ThemeOptionList";
246
+ import { ThemeOptionList } from "@forthtilliath/react-native-kit/components/theme/ThemeOptionList";
247
247
 
248
248
  <ThemeOptionList value={themePreference} onChange={setThemePreference} />;
249
249
  ```
@@ -253,7 +253,7 @@ import { ThemeOptionList } from "@forthtilliath/react-native-kit/ThemeOptionList
253
253
  Checks once per mount (e.g. app launch) whether a newer release is available, throttled to at most one real check per `minIntervalMs` (default 12h) and silent for a release the user already dismissed. Has no opinion on where "when did we last check" / "which version did the user dismiss" are persisted — both are read/written entirely through the options you pass in.
254
254
 
255
255
  ```tsx
256
- import { useUpdateCheck } from "@forthtilliath/react-native-kit/useUpdateCheck";
256
+ import { useUpdateCheck } from "@forthtilliath/react-native-kit/hooks/useUpdateCheck";
257
257
 
258
258
  const update = useUpdateCheck({
259
259
  currentVersion: Constants.expoConfig?.version ?? "0.0.0",
@@ -277,7 +277,7 @@ if (update.status === "available") {
277
277
  Dismissible banner announcing an available update: version, release notes (rendered via `ChangelogNotes`), an action button and a dismiss button. Has no opinion on what the action does (e.g. navigate to an update screen) or on how/whether dismissal is persisted.
278
278
 
279
279
  ```tsx
280
- import { UpdateAvailableBanner } from "@forthtilliath/react-native-kit/UpdateAvailableBanner";
280
+ import { UpdateAvailableBanner } from "@forthtilliath/react-native-kit/components/update/UpdateAvailableBanner";
281
281
 
282
282
  {
283
283
  update.status === "available" && (
@@ -294,19 +294,179 @@ import { UpdateAvailableBanner } from "@forthtilliath/react-native-kit/UpdateAva
294
294
  }
295
295
  ```
296
296
 
297
+ ### Settings screens (`components/settings/`)
298
+
299
+ The building blocks of a typical app "Settings" tab — menu, theme, update check, backup, about, contact, privacy — factored out so each new app stops rewriting the same ~7 screens from scratch. Expo Router (or any file-based router) still needs one physical route file per screen in your app; these just give each route file almost nothing left to write beyond rendering the matching component. None of them add new peer dependencies beyond what's already listed above.
300
+
301
+ ```tsx
302
+ // app/(tabs)/settings/index.tsx
303
+ import { router } from "expo-router";
304
+ import { SettingsMenu } from "@forthtilliath/react-native-kit/components/settings/SettingsMenu";
305
+
306
+ export default function SettingsMenuScreen() {
307
+ return (
308
+ <SettingsMenu
309
+ items={[
310
+ {
311
+ key: "backup",
312
+ emoji: "💾",
313
+ title: "Sauvegarde",
314
+ hint: "Exporter / importer tes données",
315
+ onPress: () => router.push("/settings/backup"),
316
+ },
317
+ {
318
+ key: "theme",
319
+ emoji: "🎨",
320
+ title: "Thème",
321
+ hint: "Clair, sombre, ou automatique",
322
+ onPress: () => router.push("/settings/theme"),
323
+ },
324
+ {
325
+ key: "update",
326
+ emoji: "⬇️",
327
+ title: "Mise à jour",
328
+ onPress: () => router.push("/settings/update"),
329
+ },
330
+ {
331
+ key: "about",
332
+ emoji: "ℹ️",
333
+ title: "À propos",
334
+ onPress: () => router.push("/settings/about"),
335
+ },
336
+ {
337
+ key: "contact",
338
+ emoji: "✉️",
339
+ title: "Contact",
340
+ onPress: () => router.push("/settings/contact"),
341
+ },
342
+ {
343
+ key: "privacy",
344
+ emoji: "🔒",
345
+ title: "Confidentialité",
346
+ onPress: () => router.push("/settings/privacy"),
347
+ },
348
+ ]}
349
+ />
350
+ );
351
+ }
352
+ ```
353
+
354
+ `SettingsMenu` doesn't depend on expo-router (or any other navigator) itself — each item carries its own `onPress`, so it works the same with any navigation setup.
355
+
356
+ #### `<ThemeSettingsScreen value onChange />`
357
+
358
+ Same light/dark/system data contract as `ThemeToggle`/`ThemeOptionList`, but a third visual: emoji + label + a plain-text `✓` on the active row, with a hint above explaining what "Système" does. Pick whichever of the three fits your app.
359
+
360
+ ```tsx
361
+ import { ThemeSettingsScreen } from "@forthtilliath/react-native-kit/components/settings/ThemeSettingsScreen";
362
+
363
+ <ThemeSettingsScreen value={themePreference} onChange={setThemePreference} />;
364
+ ```
365
+
366
+ #### `<UpdateSettingsScreen currentVersion checkForUpdate compareVersions downloadAndInstallApk />`
367
+
368
+ The full "check for update" screen: installed version, a manual check button, the available-update box (changelog + install button) or an up-to-date/error message, download progress, and — if `fetchReleaseHistory` is passed — a list of past releases. Checks automatically once on mount. Pairs naturally with `@forthtilliath/expo-release-updates` and `useUpdateCheck`/`UpdateAvailableBanner` above (same `checkForUpdate`/`compareVersions` you already pass those), but takes plain functions so it isn't tied to that package's exact shape.
369
+
370
+ ```tsx
371
+ import { UpdateSettingsScreen } from "@forthtilliath/react-native-kit/components/settings/UpdateSettingsScreen";
372
+
373
+ import {
374
+ compareVersions,
375
+ downloadAndInstallApk,
376
+ fetchLatestRelease,
377
+ fetchReleaseHistory,
378
+ } from "@/lib/appUpdate";
379
+
380
+ <UpdateSettingsScreen
381
+ currentVersion={Constants.expoConfig?.version ?? "?"}
382
+ checkForUpdate={fetchLatestRelease}
383
+ compareVersions={compareVersions}
384
+ downloadAndInstallApk={downloadAndInstallApk}
385
+ fetchReleaseHistory={fetchReleaseHistory}
386
+ />;
387
+ ```
388
+
389
+ #### `<BackupSettingsScreen onExport onImport reminder? />`
390
+
391
+ Export/import buttons plus an optional reminder-notification switch. Deliberately just a shell: what actually gets exported/imported is entirely app-specific (your own database tables), so `onExport`/`onImport` are required — this package can't have a sensible default for "how do I export my schema". Import asks for confirmation first (via `confirmDestructive`, from `utils/helpers/`) since it replaces existing data; `onImport` itself doesn't need to.
392
+
393
+ ```tsx
394
+ import { BackupSettingsScreen } from "@forthtilliath/react-native-kit/components/settings/BackupSettingsScreen";
395
+
396
+ <BackupSettingsScreen
397
+ onExport={shareBackup}
398
+ onImport={pickAndImportBackup}
399
+ reminder={{
400
+ enabled: reminderEnabled,
401
+ onToggle: handleToggleReminder,
402
+ intervalDays: BACKUP_REMINDER_INTERVAL_DAYS,
403
+ }}
404
+ />;
405
+ ```
406
+
407
+ Omit `reminder` entirely for an app with no backup-reminder notifications.
408
+
409
+ #### `<AboutSettingsScreen appName version description developerName />`
410
+
411
+ App name, version, one or more description paragraphs, and a "developed by" credit. `appName`/`version` are required rather than defaulted internally (keeps this package framework-agnostic) — pass `Constants.expoConfig?.name`/`.version` from the caller.
412
+
413
+ ```tsx
414
+ import { AboutSettingsScreen } from "@forthtilliath/react-native-kit/components/settings/AboutSettingsScreen";
415
+
416
+ <AboutSettingsScreen
417
+ appName={Constants.expoConfig?.name ?? "My App"}
418
+ version={Constants.expoConfig?.version ?? "?"}
419
+ description="A personal tool to do the thing."
420
+ developerName="Your Name"
421
+ />;
422
+ ```
423
+
424
+ #### `<ContactSettingsScreen email />`
425
+
426
+ A `mailto:` button plus a short "how to report a bug" hint.
427
+
428
+ ```tsx
429
+ import { ContactSettingsScreen } from "@forthtilliath/react-native-kit/components/settings/ContactSettingsScreen";
430
+
431
+ <ContactSettingsScreen email="you@example.com" />;
432
+ ```
433
+
434
+ #### `<PrivacySettingsScreen sections? />`
435
+
436
+ Ships with the standard "everything stays on this device" three sections (no data collected, local storage only, sharing is opt-in) as the default — the right fit for most of these small personal-data apps. Pass your own `sections` to fully replace it for an app with different data handling (e.g. one that syncs to a server).
437
+
438
+ ```tsx
439
+ import { PrivacySettingsScreen } from "@forthtilliath/react-native-kit/components/settings/PrivacySettingsScreen";
440
+
441
+ // Zero-config, uses the default copy:
442
+ <PrivacySettingsScreen />;
443
+
444
+ // Or fully custom:
445
+ <PrivacySettingsScreen
446
+ sections={[
447
+ {
448
+ title: "What we collect",
449
+ paragraphs: ["Only what you enter, synced to your account."],
450
+ },
451
+ ]}
452
+ />;
453
+ ```
454
+
455
+ 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.
456
+
297
457
  ### Utils (`utils/`)
298
458
 
299
459
  Framework-agnostic pure functions — no React or React Native import, usable from Node/web too.
300
460
 
301
461
  ```ts
302
- import { getPeriodStartMs } from "@forthtilliath/react-native-kit/utils/getPeriodStartMs";
303
- import { getMostRecentIds } from "@forthtilliath/react-native-kit/utils/getMostRecentIds";
304
- import { nextInCycle } from "@forthtilliath/react-native-kit/utils/nextInCycle";
305
- import { normalizeForSearch } from "@forthtilliath/react-native-kit/utils/normalizeForSearch";
306
- import { rankByNameMatch } from "@forthtilliath/react-native-kit/utils/rankByNameMatch";
307
- import { escapeCsvField } from "@forthtilliath/react-native-kit/utils/escapeCsvField";
308
- import { formatCsvNumber } from "@forthtilliath/react-native-kit/utils/formatCsvNumber";
309
- import { escapeHtml } from "@forthtilliath/react-native-kit/utils/escapeHtml";
462
+ import { getPeriodStartMs } from "@forthtilliath/react-native-kit/utils/helpers/getPeriodStartMs";
463
+ import { getMostRecentIds } from "@forthtilliath/react-native-kit/utils/helpers/getMostRecentIds";
464
+ import { nextInCycle } from "@forthtilliath/react-native-kit/utils/helpers/nextInCycle";
465
+ import { normalizeForSearch } from "@forthtilliath/react-native-kit/utils/helpers/normalizeForSearch";
466
+ import { rankByNameMatch } from "@forthtilliath/react-native-kit/utils/helpers/rankByNameMatch";
467
+ import { escapeCsvField } from "@forthtilliath/react-native-kit/utils/format/escapeCsvField";
468
+ import { formatCsvNumber } from "@forthtilliath/react-native-kit/utils/format/formatCsvNumber";
469
+ import { escapeHtml } from "@forthtilliath/react-native-kit/utils/format/escapeHtml";
310
470
  ```
311
471
 
312
472
  | Function | What it does |
@@ -0,0 +1,6 @@
1
+ export * from "./list";
2
+ export * from "./picker";
3
+ export * from "./settings";
4
+ export * from "./theme";
5
+ export * from "./update";
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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;AACxB,cAAc,UAAU,CAAC"}
@@ -0,0 +1,5 @@
1
+ export * from "./list";
2
+ export * from "./picker";
3
+ export * from "./settings";
4
+ export * from "./theme";
5
+ export * from "./update";
@@ -0,0 +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"}
@@ -0,0 +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"}
@@ -0,0 +1,3 @@
1
+ export * from "./SwipeableRow";
2
+ export * from "./Thumbnail";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/list/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC"}
@@ -0,0 +1,2 @@
1
+ export * from "./SwipeableRow";
2
+ export * from "./Thumbnail";
@@ -0,0 +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"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PickerModal.d.ts","sourceRoot":"","sources":["../../../src/components/picker/PickerModal.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VoiceSearchButton.d.ts","sourceRoot":"","sources":["../../../src/components/picker/VoiceSearchButton.tsx"],"names":[],"mappings":"AAQA,MAAM,WAAW,uBAAuB;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,qDAAqD;IACrD,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,uBAAuB,CAAC;CAClC;AAaD,wBAAgB,iBAAiB,CAAC,EAChC,QAAQ,EACR,kBAAmC,EACnC,sBAA4C,EAC5C,IAAc,EACd,MAAM,GACP,EAAE,sBAAsB,+BAwDxB"}
@@ -0,0 +1,4 @@
1
+ export * from "./PhotoPicker";
2
+ export * from "./PickerModal";
3
+ export * from "./VoiceSearchButton";
4
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/picker/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,eAAe,CAAC;AAC9B,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,3 @@
1
+ export * from "./PhotoPicker";
2
+ export * from "./PickerModal";
3
+ export * from "./VoiceSearchButton";
@@ -0,0 +1,24 @@
1
+ import type { StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ export interface AboutSettingsScreenStyles {
3
+ container?: StyleProp<ViewStyle>;
4
+ appName?: StyleProp<TextStyle>;
5
+ version?: StyleProp<TextStyle>;
6
+ separator?: StyleProp<ViewStyle>;
7
+ paragraph?: StyleProp<TextStyle>;
8
+ hint?: StyleProp<TextStyle>;
9
+ }
10
+ export interface AboutSettingsScreenLabels {
11
+ version?: (version: string) => string;
12
+ developedBy?: (name: string) => string;
13
+ }
14
+ export interface AboutSettingsScreenProps {
15
+ appName: string;
16
+ version: string;
17
+ /** One or more paragraphs describing the app, rendered in order. */
18
+ description: string | string[];
19
+ developerName: string;
20
+ labels?: AboutSettingsScreenLabels;
21
+ styles?: AboutSettingsScreenStyles;
22
+ }
23
+ export declare function AboutSettingsScreen({ appName, version, description, developerName, labels, styles, }: AboutSettingsScreenProps): import("react").JSX.Element;
24
+ //# sourceMappingURL=AboutSettingsScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AboutSettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/components/settings/AboutSettingsScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGpE,MAAM,WAAW,yBAAyB;IACxC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,OAAO,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/B,OAAO,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/B,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC7B;AAED,MAAM,WAAW,yBAAyB;IACxC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,MAAM,CAAC;IACtC,WAAW,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;CACxC;AAED,MAAM,WAAW,wBAAwB;IACvC,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,WAAW,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,yBAAyB,CAAC;IACnC,MAAM,CAAC,EAAE,yBAAyB,CAAC;CACpC;AA8BD,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,OAAO,EACP,WAAW,EACX,aAAa,EACb,MAAM,EACN,MAAM,GACP,EAAE,wBAAwB,+BA8B1B"}
@@ -0,0 +1,41 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text, View } from "react-native";
3
+ const defaultLabels = {
4
+ version: (version) => `Version ${version}`,
5
+ developedBy: (name) => `Développée par ${name}.`,
6
+ };
7
+ const defaultStyles = {
8
+ container: {},
9
+ appName: { fontSize: 22, fontWeight: "700", color: "#111827" },
10
+ version: { fontSize: 13, color: "#6b7280", marginTop: 2 },
11
+ separator: {
12
+ height: 1,
13
+ marginVertical: 16,
14
+ backgroundColor: "#e5e7eb",
15
+ },
16
+ paragraph: {
17
+ fontSize: 14,
18
+ lineHeight: 20,
19
+ color: "#111827",
20
+ marginBottom: 12,
21
+ },
22
+ hint: { fontSize: 13, color: "#6b7280" },
23
+ };
24
+ // App name, version, a short description, and a "developed by" credit — the
25
+ // standard "About" section of a settings tab. `appName`/`version` are
26
+ // required rather than defaulted from `expo-constants` so this stays
27
+ // framework-agnostic; pass `Constants.expoConfig?.name`/`.version` from the
28
+ // caller.
29
+ export function AboutSettingsScreen({ appName, version, description, developerName, labels, styles, }) {
30
+ const merged = {
31
+ container: [defaultStyles.container, styles?.container],
32
+ appName: [defaultStyles.appName, styles?.appName],
33
+ version: [defaultStyles.version, styles?.version],
34
+ separator: [defaultStyles.separator, styles?.separator],
35
+ paragraph: [defaultStyles.paragraph, styles?.paragraph],
36
+ hint: [defaultStyles.hint, styles?.hint],
37
+ };
38
+ const t = { ...defaultLabels, ...labels };
39
+ const paragraphs = Array.isArray(description) ? description : [description];
40
+ return (_jsxs(View, { style: merged.container, children: [_jsx(Text, { style: merged.appName, children: appName }), _jsx(Text, { style: merged.version, children: t.version(version) }), _jsx(View, { style: merged.separator }), paragraphs.map((paragraph) => (_jsx(Text, { style: merged.paragraph, children: paragraph }, paragraph))), _jsx(View, { style: merged.separator }), _jsx(Text, { style: merged.hint, children: t.developedBy(developerName) })] }));
41
+ }
@@ -0,0 +1,52 @@
1
+ import type { StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ export interface BackupSettingsScreenReminder {
3
+ enabled: boolean;
4
+ /** Set while a permission request or the toggle's own persistence is in flight. */
5
+ busy?: boolean;
6
+ onToggle: (value: boolean) => void;
7
+ /** How often the reminder fires, only used to word the hint (e.g. "every 7 days"). */
8
+ intervalDays: number;
9
+ }
10
+ export interface BackupSettingsScreenStyles {
11
+ container?: StyleProp<ViewStyle>;
12
+ hint?: StyleProp<TextStyle>;
13
+ row?: StyleProp<ViewStyle>;
14
+ button?: StyleProp<ViewStyle>;
15
+ buttonDisabled?: StyleProp<ViewStyle>;
16
+ buttonText?: StyleProp<TextStyle>;
17
+ reminderRow?: StyleProp<ViewStyle>;
18
+ reminderTextColumn?: StyleProp<ViewStyle>;
19
+ reminderLabel?: StyleProp<TextStyle>;
20
+ reminderHint?: StyleProp<TextStyle>;
21
+ activityIndicatorColor?: string;
22
+ }
23
+ export interface BackupSettingsScreenLabels {
24
+ hint?: string;
25
+ exportButton?: string;
26
+ importButton?: string;
27
+ importConfirmTitle?: string;
28
+ importConfirmMessage?: string;
29
+ importConfirmLabel?: string;
30
+ importConfirmCancelLabel?: string;
31
+ reminderLabel?: string;
32
+ reminderHint?: (intervalDays: number) => string;
33
+ exportErrorTitle?: string;
34
+ importErrorTitle?: string;
35
+ genericErrorMessage?: string;
36
+ }
37
+ export interface BackupSettingsScreenProps {
38
+ /** Exports/shares the app's data however it sees fit (e.g. a JSON file via the native share sheet). */
39
+ onExport: () => Promise<void>;
40
+ /**
41
+ * Imports data from a file the user picks, replacing what's currently
42
+ * stored. Only called after the user confirms the destructive-replace
43
+ * warning — this doesn't need to show its own confirmation.
44
+ */
45
+ onImport: () => Promise<void>;
46
+ /** Omit entirely to not show a reminder toggle at all. */
47
+ reminder?: BackupSettingsScreenReminder;
48
+ labels?: BackupSettingsScreenLabels;
49
+ styles?: BackupSettingsScreenStyles;
50
+ }
51
+ export declare function BackupSettingsScreen({ onExport, onImport, reminder, labels, styles, }: BackupSettingsScreenProps): import("react").JSX.Element;
52
+ //# sourceMappingURL=BackupSettingsScreen.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"BackupSettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/components/settings/BackupSettingsScreen.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAYpE,MAAM,WAAW,4BAA4B;IAC3C,OAAO,EAAE,OAAO,CAAC;IACjB,mFAAmF;IACnF,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IACnC,sFAAsF;IACtF,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,0BAA0B;IACzC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5B,GAAG,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3B,MAAM,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC9B,cAAc,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACtC,UAAU,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,kBAAkB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1C,aAAa,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACrC,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACpC,sBAAsB,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,0BAA0B;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,YAAY,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,MAAM,CAAC;IAChD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,yBAAyB;IACxC,uGAAuG;IACvG,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9B,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,4BAA4B,CAAC;IACxC,MAAM,CAAC,EAAE,0BAA0B,CAAC;IACpC,MAAM,CAAC,EAAE,0BAA0B,CAAC;CACrC;AAiED,wBAAgB,oBAAoB,CAAC,EACnC,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,MAAM,EACN,MAAM,GACP,EAAE,yBAAyB,+BA8H3B"}
@@ -0,0 +1,120 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from "react";
3
+ import { ActivityIndicator, Alert, Pressable, Switch, Text, View, } from "react-native";
4
+ import { confirmDestructive } from "../../utils/helpers/confirmDestructive.js";
5
+ const defaultLabels = {
6
+ hint: "Les données sont stockées uniquement sur cet appareil et sont perdues en cas de réinstallation ou de mise à jour incompatible. Exporte-les régulièrement pour pouvoir les restaurer.",
7
+ exportButton: "⬆️ Exporter",
8
+ importButton: "⬇️ Importer",
9
+ importConfirmTitle: "Importer une sauvegarde ?",
10
+ importConfirmMessage: "Toutes les données actuelles seront remplacées par celles du fichier choisi.",
11
+ importConfirmLabel: "Choisir un fichier",
12
+ importConfirmCancelLabel: "Annuler",
13
+ reminderLabel: "🔔 Rappel de sauvegarde",
14
+ reminderHint: undefined,
15
+ exportErrorTitle: "Échec de l'export",
16
+ importErrorTitle: "Échec de l'import",
17
+ genericErrorMessage: "Une erreur inconnue s'est produite.",
18
+ };
19
+ const defaultStyles = {
20
+ container: {},
21
+ hint: { fontSize: 13, color: "#6b7280", marginBottom: 16 },
22
+ row: { flexDirection: "row", gap: 10 },
23
+ button: {
24
+ flex: 1,
25
+ paddingVertical: 12,
26
+ borderRadius: 8,
27
+ borderWidth: 1,
28
+ borderColor: "#2563eb",
29
+ alignItems: "center",
30
+ justifyContent: "center",
31
+ },
32
+ buttonDisabled: { opacity: 0.5 },
33
+ buttonText: { color: "#2563eb", fontWeight: "700" },
34
+ reminderRow: {
35
+ flexDirection: "row",
36
+ alignItems: "center",
37
+ gap: 12,
38
+ marginTop: 24,
39
+ },
40
+ reminderTextColumn: { flex: 1 },
41
+ reminderLabel: {
42
+ fontSize: 15,
43
+ fontWeight: "600",
44
+ color: "#111827",
45
+ marginBottom: 4,
46
+ },
47
+ reminderHint: { fontSize: 13, color: "#6b7280" },
48
+ activityIndicatorColor: undefined,
49
+ };
50
+ function errorMessage(err, fallback) {
51
+ return err instanceof Error ? err.message : fallback;
52
+ }
53
+ // Export/import buttons plus an optional reminder-notification toggle — the
54
+ // standard "Backup" section of a settings tab. Deliberately just a shell:
55
+ // what gets exported/imported is entirely app-specific (its own database
56
+ // tables), so `onExport`/`onImport` are required props, not something this
57
+ // package could have a sensible default for.
58
+ export function BackupSettingsScreen({ onExport, onImport, reminder, labels, styles, }) {
59
+ const [exporting, setExporting] = useState(false);
60
+ const [importing, setImporting] = useState(false);
61
+ const merged = {
62
+ container: [defaultStyles.container, styles?.container],
63
+ hint: [defaultStyles.hint, styles?.hint],
64
+ row: [defaultStyles.row, styles?.row],
65
+ button: [defaultStyles.button, styles?.button],
66
+ buttonDisabled: [defaultStyles.buttonDisabled, styles?.buttonDisabled],
67
+ buttonText: [defaultStyles.buttonText, styles?.buttonText],
68
+ reminderRow: [defaultStyles.reminderRow, styles?.reminderRow],
69
+ reminderTextColumn: [
70
+ defaultStyles.reminderTextColumn,
71
+ styles?.reminderTextColumn,
72
+ ],
73
+ reminderLabel: [defaultStyles.reminderLabel, styles?.reminderLabel],
74
+ reminderHint: [defaultStyles.reminderHint, styles?.reminderHint],
75
+ };
76
+ const t = { ...defaultLabels, ...labels };
77
+ const activityIndicatorColor = styles?.activityIndicatorColor ?? defaultStyles.activityIndicatorColor;
78
+ async function handleExport() {
79
+ if (exporting)
80
+ return;
81
+ setExporting(true);
82
+ try {
83
+ await onExport();
84
+ }
85
+ catch (err) {
86
+ Alert.alert(t.exportErrorTitle, errorMessage(err, t.genericErrorMessage));
87
+ }
88
+ finally {
89
+ setExporting(false);
90
+ }
91
+ }
92
+ function handleImportPress() {
93
+ if (importing)
94
+ return;
95
+ confirmDestructive(t.importConfirmTitle, () => {
96
+ void (async () => {
97
+ setImporting(true);
98
+ try {
99
+ await onImport();
100
+ }
101
+ catch (err) {
102
+ Alert.alert(t.importErrorTitle, errorMessage(err, t.genericErrorMessage));
103
+ }
104
+ finally {
105
+ setImporting(false);
106
+ }
107
+ })();
108
+ }, {
109
+ message: t.importConfirmMessage,
110
+ cancelLabel: t.importConfirmCancelLabel,
111
+ confirmLabel: t.importConfirmLabel,
112
+ });
113
+ }
114
+ return (_jsxs(View, { style: merged.container, children: [_jsx(Text, { style: merged.hint, children: t.hint }), _jsxs(View, { style: merged.row, children: [_jsx(Pressable, { style: [merged.button, exporting && merged.buttonDisabled], disabled: exporting, onPress: () => {
115
+ void handleExport();
116
+ }, accessibilityRole: "button", accessibilityLabel: t.exportButton, children: exporting ? (_jsx(ActivityIndicator, { color: activityIndicatorColor })) : (_jsx(Text, { style: merged.buttonText, children: t.exportButton })) }), _jsx(Pressable, { style: [merged.button, importing && merged.buttonDisabled], disabled: importing, onPress: handleImportPress, accessibilityRole: "button", accessibilityLabel: t.importButton, children: importing ? (_jsx(ActivityIndicator, { color: activityIndicatorColor })) : (_jsx(Text, { style: merged.buttonText, children: t.importButton })) })] }), reminder && (_jsxs(Pressable, { style: merged.reminderRow, disabled: reminder.busy, onPress: () => {
117
+ reminder.onToggle(!reminder.enabled);
118
+ }, accessibilityRole: "switch", accessibilityState: { checked: reminder.enabled }, accessibilityLabel: t.reminderLabel, children: [_jsxs(View, { style: merged.reminderTextColumn, children: [_jsx(Text, { style: merged.reminderLabel, children: t.reminderLabel }), _jsx(Text, { style: merged.reminderHint, children: t.reminderHint?.(reminder.intervalDays) ??
119
+ `Une notification tous les ${String(reminder.intervalDays)} jours pour penser à exporter.` })] }), reminder.busy ? (_jsx(ActivityIndicator, { color: activityIndicatorColor })) : (_jsx(Switch, { value: reminder.enabled, onValueChange: reminder.onToggle }))] }))] }));
120
+ }