@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.
- package/README.md +183 -23
- package/dist/components/index.d.ts +6 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +5 -0
- package/dist/components/list/SwipeableRow.d.ts.map +1 -0
- package/dist/components/list/Thumbnail.d.ts.map +1 -0
- package/dist/components/list/index.d.ts +3 -0
- package/dist/components/list/index.d.ts.map +1 -0
- package/dist/components/list/index.js +2 -0
- package/dist/components/picker/PhotoPicker.d.ts.map +1 -0
- package/dist/components/picker/PickerModal.d.ts.map +1 -0
- package/dist/components/picker/VoiceSearchButton.d.ts.map +1 -0
- package/dist/components/picker/index.d.ts +4 -0
- package/dist/components/picker/index.d.ts.map +1 -0
- package/dist/components/picker/index.js +3 -0
- package/dist/components/settings/AboutSettingsScreen.d.ts +24 -0
- package/dist/components/settings/AboutSettingsScreen.d.ts.map +1 -0
- package/dist/components/settings/AboutSettingsScreen.js +41 -0
- package/dist/components/settings/BackupSettingsScreen.d.ts +52 -0
- package/dist/components/settings/BackupSettingsScreen.d.ts.map +1 -0
- package/dist/components/settings/BackupSettingsScreen.js +120 -0
- package/dist/components/settings/ContactSettingsScreen.d.ts +19 -0
- package/dist/components/settings/ContactSettingsScreen.d.ts.map +1 -0
- package/dist/components/settings/ContactSettingsScreen.js +39 -0
- package/dist/components/settings/PrivacySettingsScreen.d.ts +23 -0
- package/dist/components/settings/PrivacySettingsScreen.d.ts.map +1 -0
- package/dist/components/settings/PrivacySettingsScreen.js +51 -0
- package/dist/components/settings/SettingsMenu.d.ts +24 -0
- package/dist/components/settings/SettingsMenu.d.ts.map +1 -0
- package/dist/components/settings/SettingsMenu.js +40 -0
- package/dist/components/settings/ThemeSettingsScreen.d.ts +32 -0
- package/dist/components/settings/ThemeSettingsScreen.d.ts.map +1 -0
- package/dist/components/settings/ThemeSettingsScreen.js +71 -0
- package/dist/components/settings/UpdateSettingsScreen.d.ts +62 -0
- package/dist/components/settings/UpdateSettingsScreen.d.ts.map +1 -0
- package/dist/components/settings/UpdateSettingsScreen.js +178 -0
- package/dist/components/settings/index.d.ts +8 -0
- package/dist/components/settings/index.d.ts.map +1 -0
- package/dist/components/settings/index.js +7 -0
- package/dist/{ThemeOptionList.d.ts → components/theme/ThemeOptionList.d.ts} +1 -1
- package/dist/components/theme/ThemeOptionList.d.ts.map +1 -0
- package/dist/{ThemeToggle.d.ts → components/theme/ThemeToggle.d.ts} +1 -1
- package/dist/components/theme/ThemeToggle.d.ts.map +1 -0
- package/dist/components/theme/index.d.ts +3 -0
- package/dist/components/theme/index.d.ts.map +1 -0
- package/dist/components/theme/index.js +2 -0
- package/dist/components/update/ChangelogNotes.d.ts.map +1 -0
- package/dist/components/update/UpdateAvailableBanner.d.ts.map +1 -0
- package/dist/components/update/index.d.ts +3 -0
- package/dist/components/update/index.d.ts.map +1 -0
- package/dist/components/update/index.js +2 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +4 -0
- package/dist/hooks/useDebouncedChange.d.ts.map +1 -0
- package/dist/hooks/useEffectiveColorScheme.d.ts.map +1 -0
- package/dist/hooks/useSubmitGuard.d.ts.map +1 -0
- package/dist/hooks/useUpdateCheck.d.ts.map +1 -0
- package/dist/index.d.ts +29 -22
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +33 -25
- package/dist/utils/format/escapeCsvField.d.ts.map +1 -0
- package/dist/utils/format/escapeHtml.d.ts.map +1 -0
- package/dist/utils/format/formatCsvNumber.d.ts.map +1 -0
- package/dist/utils/format/index.d.ts +4 -0
- package/dist/utils/format/index.d.ts.map +1 -0
- package/dist/utils/format/index.js +3 -0
- package/dist/utils/helpers/confirmDestructive.d.ts.map +1 -0
- package/dist/utils/helpers/getMostRecentIds.d.ts.map +1 -0
- package/dist/utils/helpers/getPeriodStartMs.d.ts.map +1 -0
- package/dist/utils/helpers/index.d.ts +7 -0
- package/dist/utils/helpers/index.d.ts.map +1 -0
- package/dist/utils/helpers/index.js +6 -0
- package/dist/utils/helpers/nextInCycle.d.ts.map +1 -0
- package/dist/utils/helpers/normalizeForSearch.d.ts.map +1 -0
- package/dist/utils/helpers/rankByNameMatch.d.ts.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +2 -0
- package/package.json +7 -7
- package/dist/ChangelogNotes.d.ts.map +0 -1
- package/dist/PhotoPicker.d.ts.map +0 -1
- package/dist/PickerModal.d.ts.map +0 -1
- package/dist/SwipeableRow.d.ts.map +0 -1
- package/dist/ThemeOptionList.d.ts.map +0 -1
- package/dist/ThemeToggle.d.ts.map +0 -1
- package/dist/Thumbnail.d.ts.map +0 -1
- package/dist/UpdateAvailableBanner.d.ts.map +0 -1
- package/dist/VoiceSearchButton.d.ts.map +0 -1
- package/dist/confirmDestructive.d.ts.map +0 -1
- package/dist/useDebouncedChange.d.ts.map +0 -1
- package/dist/useEffectiveColorScheme.d.ts.map +0 -1
- package/dist/useSubmitGuard.d.ts.map +0 -1
- package/dist/useUpdateCheck.d.ts.map +0 -1
- package/dist/utils/escapeCsvField.d.ts.map +0 -1
- package/dist/utils/escapeHtml.d.ts.map +0 -1
- package/dist/utils/formatCsvNumber.d.ts.map +0 -1
- package/dist/utils/getMostRecentIds.d.ts.map +0 -1
- package/dist/utils/getPeriodStartMs.d.ts.map +0 -1
- package/dist/utils/nextInCycle.d.ts.map +0 -1
- package/dist/utils/normalizeForSearch.d.ts.map +0 -1
- package/dist/utils/rankByNameMatch.d.ts.map +0 -1
- /package/dist/{SwipeableRow.d.ts → components/list/SwipeableRow.d.ts} +0 -0
- /package/dist/{SwipeableRow.js → components/list/SwipeableRow.js} +0 -0
- /package/dist/{Thumbnail.d.ts → components/list/Thumbnail.d.ts} +0 -0
- /package/dist/{Thumbnail.js → components/list/Thumbnail.js} +0 -0
- /package/dist/{PhotoPicker.d.ts → components/picker/PhotoPicker.d.ts} +0 -0
- /package/dist/{PhotoPicker.js → components/picker/PhotoPicker.js} +0 -0
- /package/dist/{PickerModal.d.ts → components/picker/PickerModal.d.ts} +0 -0
- /package/dist/{PickerModal.js → components/picker/PickerModal.js} +0 -0
- /package/dist/{VoiceSearchButton.d.ts → components/picker/VoiceSearchButton.d.ts} +0 -0
- /package/dist/{VoiceSearchButton.js → components/picker/VoiceSearchButton.js} +0 -0
- /package/dist/{ThemeOptionList.js → components/theme/ThemeOptionList.js} +0 -0
- /package/dist/{ThemeToggle.js → components/theme/ThemeToggle.js} +0 -0
- /package/dist/{ChangelogNotes.d.ts → components/update/ChangelogNotes.d.ts} +0 -0
- /package/dist/{ChangelogNotes.js → components/update/ChangelogNotes.js} +0 -0
- /package/dist/{UpdateAvailableBanner.d.ts → components/update/UpdateAvailableBanner.d.ts} +0 -0
- /package/dist/{UpdateAvailableBanner.js → components/update/UpdateAvailableBanner.js} +0 -0
- /package/dist/{useDebouncedChange.d.ts → hooks/useDebouncedChange.d.ts} +0 -0
- /package/dist/{useDebouncedChange.js → hooks/useDebouncedChange.js} +0 -0
- /package/dist/{useEffectiveColorScheme.d.ts → hooks/useEffectiveColorScheme.d.ts} +0 -0
- /package/dist/{useEffectiveColorScheme.js → hooks/useEffectiveColorScheme.js} +0 -0
- /package/dist/{useSubmitGuard.d.ts → hooks/useSubmitGuard.d.ts} +0 -0
- /package/dist/{useSubmitGuard.js → hooks/useSubmitGuard.js} +0 -0
- /package/dist/{useUpdateCheck.d.ts → hooks/useUpdateCheck.d.ts} +0 -0
- /package/dist/{useUpdateCheck.js → hooks/useUpdateCheck.js} +0 -0
- /package/dist/utils/{escapeCsvField.d.ts → format/escapeCsvField.d.ts} +0 -0
- /package/dist/utils/{escapeCsvField.js → format/escapeCsvField.js} +0 -0
- /package/dist/utils/{escapeHtml.d.ts → format/escapeHtml.d.ts} +0 -0
- /package/dist/utils/{escapeHtml.js → format/escapeHtml.js} +0 -0
- /package/dist/utils/{formatCsvNumber.d.ts → format/formatCsvNumber.d.ts} +0 -0
- /package/dist/utils/{formatCsvNumber.js → format/formatCsvNumber.js} +0 -0
- /package/dist/{confirmDestructive.d.ts → utils/helpers/confirmDestructive.d.ts} +0 -0
- /package/dist/{confirmDestructive.js → utils/helpers/confirmDestructive.js} +0 -0
- /package/dist/utils/{getMostRecentIds.d.ts → helpers/getMostRecentIds.d.ts} +0 -0
- /package/dist/utils/{getMostRecentIds.js → helpers/getMostRecentIds.js} +0 -0
- /package/dist/utils/{getPeriodStartMs.d.ts → helpers/getPeriodStartMs.d.ts} +0 -0
- /package/dist/utils/{getPeriodStartMs.js → helpers/getPeriodStartMs.js} +0 -0
- /package/dist/utils/{nextInCycle.d.ts → helpers/nextInCycle.d.ts} +0 -0
- /package/dist/utils/{nextInCycle.js → helpers/nextInCycle.js} +0 -0
- /package/dist/utils/{normalizeForSearch.d.ts → helpers/normalizeForSearch.d.ts} +0 -0
- /package/dist/utils/{normalizeForSearch.js → helpers/normalizeForSearch.js} +0 -0
- /package/dist/utils/{rankByNameMatch.d.ts → helpers/rankByNameMatch.d.ts} +0 -0
- /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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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 @@
|
|
|
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,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
|
+
}
|