@forthtilliath/react-native-kit 0.10.0 → 0.12.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 +116 -10
- package/dist/components/settings/AboutSettingsScreen.d.ts +10 -2
- package/dist/components/settings/AboutSettingsScreen.d.ts.map +1 -1
- package/dist/components/settings/AboutSettingsScreen.js +13 -5
- package/dist/components/settings/BackupSettingsScreen.d.ts +33 -1
- package/dist/components/settings/BackupSettingsScreen.d.ts.map +1 -1
- package/dist/components/settings/BackupSettingsScreen.js +58 -6
- package/dist/components/settings/ContactSettingsScreen.d.ts +22 -1
- package/dist/components/settings/ContactSettingsScreen.d.ts.map +1 -1
- package/dist/components/settings/ContactSettingsScreen.js +28 -5
- package/dist/components/settings/SettingsMenu.d.ts +44 -4
- package/dist/components/settings/SettingsMenu.d.ts.map +1 -1
- package/dist/components/settings/SettingsMenu.js +45 -6
- package/dist/components/settings/ThemeSettingsScreen.d.ts +20 -1
- package/dist/components/settings/ThemeSettingsScreen.d.ts.map +1 -1
- package/dist/components/settings/ThemeSettingsScreen.js +22 -7
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -307,44 +307,48 @@ export default function SettingsMenuScreen() {
|
|
|
307
307
|
return (
|
|
308
308
|
<SettingsMenu
|
|
309
309
|
items={[
|
|
310
|
+
// `key` doubles as the lookup into a built-in default icon/label
|
|
311
|
+
// pairing for the 6 sections above — no `emoji`/`icon` needed here.
|
|
310
312
|
{
|
|
311
313
|
key: "backup",
|
|
312
|
-
emoji: "💾",
|
|
313
314
|
title: "Sauvegarde",
|
|
314
315
|
hint: "Exporter / importer tes données",
|
|
315
316
|
onPress: () => router.push("/settings/backup"),
|
|
316
317
|
},
|
|
317
318
|
{
|
|
318
319
|
key: "theme",
|
|
319
|
-
emoji: "🎨",
|
|
320
320
|
title: "Thème",
|
|
321
321
|
hint: "Clair, sombre, ou automatique",
|
|
322
322
|
onPress: () => router.push("/settings/theme"),
|
|
323
323
|
},
|
|
324
324
|
{
|
|
325
325
|
key: "update",
|
|
326
|
-
emoji: "⬇️",
|
|
327
326
|
title: "Mise à jour",
|
|
328
327
|
onPress: () => router.push("/settings/update"),
|
|
329
328
|
},
|
|
330
329
|
{
|
|
331
330
|
key: "about",
|
|
332
|
-
emoji: "ℹ️",
|
|
333
331
|
title: "À propos",
|
|
334
332
|
onPress: () => router.push("/settings/about"),
|
|
335
333
|
},
|
|
336
334
|
{
|
|
337
335
|
key: "contact",
|
|
338
|
-
emoji: "✉️",
|
|
339
336
|
title: "Contact",
|
|
340
337
|
onPress: () => router.push("/settings/contact"),
|
|
341
338
|
},
|
|
342
339
|
{
|
|
343
340
|
key: "privacy",
|
|
344
|
-
emoji: "🔒",
|
|
345
341
|
title: "Confidentialité",
|
|
346
342
|
onPress: () => router.push("/settings/privacy"),
|
|
347
343
|
},
|
|
344
|
+
// An app-specific section: no default exists for an arbitrary key,
|
|
345
|
+
// so `emoji`/`icon` (and `hint`, if you want one) are yours to set.
|
|
346
|
+
{
|
|
347
|
+
key: "profile",
|
|
348
|
+
emoji: "🙋",
|
|
349
|
+
title: "Mon nom",
|
|
350
|
+
onPress: () => router.push("/settings/profile"),
|
|
351
|
+
},
|
|
348
352
|
]}
|
|
349
353
|
/>
|
|
350
354
|
);
|
|
@@ -353,14 +357,76 @@ export default function SettingsMenuScreen() {
|
|
|
353
357
|
|
|
354
358
|
`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
359
|
|
|
360
|
+
Two independent style knobs, both usable on their own:
|
|
361
|
+
|
|
362
|
+
- **Icon flavor** — each item can set its own `emoji` (naturally multicolor) or `icon` (an `Ionicons` glyph, name in `styles.iconColor`) to override its default; `defaultIconKind` (`"emoji"` by default, or `"icon"`) picks which flavor of the _default_ table applies to items that don't set either.
|
|
363
|
+
- **Hints** — set `showHints={false}` to hide every item's `hint` at once, for a denser, icon-only-style menu.
|
|
364
|
+
|
|
365
|
+
```tsx
|
|
366
|
+
<SettingsMenu
|
|
367
|
+
items={[
|
|
368
|
+
{
|
|
369
|
+
key: "backup",
|
|
370
|
+
title: "Sauvegarde",
|
|
371
|
+
onPress: () => router.push("/settings/backup"),
|
|
372
|
+
},
|
|
373
|
+
]}
|
|
374
|
+
defaultIconKind="icon"
|
|
375
|
+
showHints={false}
|
|
376
|
+
styles={{
|
|
377
|
+
row: { backgroundColor: colors.primary, borderWidth: 0 },
|
|
378
|
+
iconColor: "#fff",
|
|
379
|
+
title: { color: "#fff" },
|
|
380
|
+
}}
|
|
381
|
+
/>
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
Pass `groups` instead of `items` to render rows under optional section headers (e.g. separating app-specific settings from the standard ones) — the two are mutually exclusive:
|
|
385
|
+
|
|
386
|
+
```tsx
|
|
387
|
+
<SettingsMenu
|
|
388
|
+
groups={[
|
|
389
|
+
{
|
|
390
|
+
title: "Réglages de calcul",
|
|
391
|
+
items: [
|
|
392
|
+
{
|
|
393
|
+
key: "ratio",
|
|
394
|
+
emoji: "🧮",
|
|
395
|
+
title: "Ratio glucidique",
|
|
396
|
+
onPress: () => router.push("/settings/ratio"),
|
|
397
|
+
},
|
|
398
|
+
],
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
// No `title` renders the group with no header at all, e.g. for a
|
|
402
|
+
// leading group of app-specific items above the standard ones.
|
|
403
|
+
items: [
|
|
404
|
+
{
|
|
405
|
+
key: "backup",
|
|
406
|
+
title: "Sauvegarde",
|
|
407
|
+
onPress: () => router.push("/settings/backup"),
|
|
408
|
+
},
|
|
409
|
+
],
|
|
410
|
+
},
|
|
411
|
+
]}
|
|
412
|
+
/>
|
|
413
|
+
```
|
|
414
|
+
|
|
356
415
|
#### `<ThemeSettingsScreen value onChange />`
|
|
357
416
|
|
|
358
|
-
Same light/dark/system data contract as `ThemeToggle`/`ThemeOptionList
|
|
417
|
+
Same light/dark/system data contract as `ThemeToggle`/`ThemeOptionList`. Two visuals, picked via `variant`: `"emoji"` (default) — emoji + label + a plain-text `✓` on the active row — or `"icon"` — `Ionicons` glyphs in one shared color (`styles.iconColor`/`iconColorActive`) + an `Ionicons` checkmark, matching `SettingsMenu`'s `defaultIconKind="icon"` look. `showHint={false}` hides the hint above the list.
|
|
359
418
|
|
|
360
419
|
```tsx
|
|
361
420
|
import { ThemeSettingsScreen } from "@forthtilliath/react-native-kit/components/settings/ThemeSettingsScreen";
|
|
362
421
|
|
|
363
422
|
<ThemeSettingsScreen value={themePreference} onChange={setThemePreference} />;
|
|
423
|
+
|
|
424
|
+
// Or, matching an icon-flavored SettingsMenu:
|
|
425
|
+
<ThemeSettingsScreen
|
|
426
|
+
value={themePreference}
|
|
427
|
+
onChange={setThemePreference}
|
|
428
|
+
variant="icon"
|
|
429
|
+
/>;
|
|
364
430
|
```
|
|
365
431
|
|
|
366
432
|
#### `<UpdateSettingsScreen currentVersion checkForUpdate compareVersions downloadAndInstallApk />`
|
|
@@ -406,9 +472,21 @@ import { BackupSettingsScreen } from "@forthtilliath/react-native-kit/components
|
|
|
406
472
|
|
|
407
473
|
Omit `reminder` entirely for an app with no backup-reminder notifications.
|
|
408
474
|
|
|
409
|
-
|
|
475
|
+
Three more knobs for apps whose backup section doesn't fit the compact 2-button shell: an `info` box above everything (e.g. "last auto-backup: ..."), `layout="sections"` for a titled, full-width, stacked layout instead of side-by-side buttons, and `icons` for a leading `Ionicons` glyph on either button.
|
|
476
|
+
|
|
477
|
+
```tsx
|
|
478
|
+
<BackupSettingsScreen
|
|
479
|
+
onExport={shareBackup}
|
|
480
|
+
onImport={pickAndImportBackup}
|
|
481
|
+
info={{ label: "Dernière sauvegarde auto", value: lastAutoBackupLabel }}
|
|
482
|
+
layout="sections"
|
|
483
|
+
icons={{ export: "cloud-upload-outline", import: "cloud-download-outline" }}
|
|
484
|
+
/>
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
#### `<AboutSettingsScreen appName version description developerName? sections? />`
|
|
410
488
|
|
|
411
|
-
App name, version, one or more description paragraphs, and
|
|
489
|
+
App name, version, one or more description paragraphs, and an optional "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
490
|
|
|
413
491
|
```tsx
|
|
414
492
|
import { AboutSettingsScreen } from "@forthtilliath/react-native-kit/components/settings/AboutSettingsScreen";
|
|
@@ -421,7 +499,23 @@ import { AboutSettingsScreen } from "@forthtilliath/react-native-kit/components/
|
|
|
421
499
|
/>;
|
|
422
500
|
```
|
|
423
501
|
|
|
424
|
-
|
|
502
|
+
Omit `developerName` for an app with no credit line to show. Pass `sections` for extra titled blocks after the description — e.g. a medical/legal disclaimer:
|
|
503
|
+
|
|
504
|
+
```tsx
|
|
505
|
+
<AboutSettingsScreen
|
|
506
|
+
appName="GlucoDose"
|
|
507
|
+
version="1.2.0"
|
|
508
|
+
description="Un outil personnel de calcul de dose."
|
|
509
|
+
sections={[
|
|
510
|
+
{
|
|
511
|
+
title: "Avertissement médical",
|
|
512
|
+
paragraphs: ["Cet outil ne remplace pas un avis médical professionnel."],
|
|
513
|
+
},
|
|
514
|
+
]}
|
|
515
|
+
/>
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
#### `<ContactSettingsScreen email actions? emailIcon? />`
|
|
425
519
|
|
|
426
520
|
A `mailto:` button plus a short "how to report a bug" hint.
|
|
427
521
|
|
|
@@ -431,6 +525,18 @@ import { ContactSettingsScreen } from "@forthtilliath/react-native-kit/component
|
|
|
431
525
|
<ContactSettingsScreen email="you@example.com" />;
|
|
432
526
|
```
|
|
433
527
|
|
|
528
|
+
Pass `actions` for extra rows below the email one (share the app, a donation link...) — switches the whole section from the single bordered button to a rows layout, email included:
|
|
529
|
+
|
|
530
|
+
```tsx
|
|
531
|
+
<ContactSettingsScreen
|
|
532
|
+
email="you@example.com"
|
|
533
|
+
actions={[
|
|
534
|
+
{ icon: "share-outline", label: "Partager l'app", onPress: shareApp },
|
|
535
|
+
{ icon: "star-outline", label: "Noter l'app", onPress: openStoreListing },
|
|
536
|
+
]}
|
|
537
|
+
/>
|
|
538
|
+
```
|
|
539
|
+
|
|
434
540
|
#### `<PrivacySettingsScreen sections? />`
|
|
435
541
|
|
|
436
542
|
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).
|
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
import type { StyleProp, TextStyle, ViewStyle } from "react-native";
|
|
2
|
+
export interface AboutSettingsScreenSection {
|
|
3
|
+
title: string;
|
|
4
|
+
paragraphs: string[];
|
|
5
|
+
}
|
|
2
6
|
export interface AboutSettingsScreenStyles {
|
|
3
7
|
container?: StyleProp<ViewStyle>;
|
|
4
8
|
appName?: StyleProp<TextStyle>;
|
|
5
9
|
version?: StyleProp<TextStyle>;
|
|
6
10
|
separator?: StyleProp<ViewStyle>;
|
|
7
11
|
paragraph?: StyleProp<TextStyle>;
|
|
12
|
+
sectionTitle?: StyleProp<TextStyle>;
|
|
8
13
|
hint?: StyleProp<TextStyle>;
|
|
9
14
|
}
|
|
10
15
|
export interface AboutSettingsScreenLabels {
|
|
@@ -16,9 +21,12 @@ export interface AboutSettingsScreenProps {
|
|
|
16
21
|
version: string;
|
|
17
22
|
/** One or more paragraphs describing the app, rendered in order. */
|
|
18
23
|
description: string | string[];
|
|
19
|
-
|
|
24
|
+
/** Extra titled sections after the description, e.g. a medical/legal disclaimer. */
|
|
25
|
+
sections?: AboutSettingsScreenSection[];
|
|
26
|
+
/** Omit to not show a "developed by" credit line at all. */
|
|
27
|
+
developerName?: string;
|
|
20
28
|
labels?: AboutSettingsScreenLabels;
|
|
21
29
|
styles?: AboutSettingsScreenStyles;
|
|
22
30
|
}
|
|
23
|
-
export declare function AboutSettingsScreen({ appName, version, description, developerName, labels, styles, }: AboutSettingsScreenProps): import("react").JSX.Element;
|
|
31
|
+
export declare function AboutSettingsScreen({ appName, version, description, sections, developerName, labels, styles, }: AboutSettingsScreenProps): import("react").JSX.Element;
|
|
24
32
|
//# sourceMappingURL=AboutSettingsScreen.d.ts.map
|
|
@@ -1 +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;
|
|
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,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,EAAE,CAAC;CACtB;AAED,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,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACpC,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,oFAAoF;IACpF,QAAQ,CAAC,EAAE,0BAA0B,EAAE,CAAC;IACxC,4DAA4D;IAC5D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,MAAM,CAAC,EAAE,yBAAyB,CAAC;IACnC,MAAM,CAAC,EAAE,yBAAyB,CAAC;CACpC;AAqCD,wBAAgB,mBAAmB,CAAC,EAClC,OAAO,EACP,OAAO,EACP,WAAW,EACX,QAAQ,EACR,aAAa,EACb,MAAM,EACN,MAAM,GACP,EAAE,wBAAwB,+BA8C1B"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { Text, View } from "react-native";
|
|
3
3
|
const defaultLabels = {
|
|
4
4
|
version: (version) => `Version ${version}`,
|
|
@@ -19,23 +19,31 @@ const defaultStyles = {
|
|
|
19
19
|
color: "#111827",
|
|
20
20
|
marginBottom: 12,
|
|
21
21
|
},
|
|
22
|
+
sectionTitle: {
|
|
23
|
+
fontSize: 15,
|
|
24
|
+
fontWeight: "700",
|
|
25
|
+
color: "#111827",
|
|
26
|
+
marginBottom: 8,
|
|
27
|
+
},
|
|
22
28
|
hint: { fontSize: 13, color: "#6b7280" },
|
|
23
29
|
};
|
|
24
|
-
// App name, version, a short description,
|
|
25
|
-
//
|
|
30
|
+
// App name, version, a short description, optional extra titled sections
|
|
31
|
+
// (e.g. a medical/legal disclaimer), and an optional "developed by" credit —
|
|
32
|
+
// the standard "About" section of a settings tab. `appName`/`version` are
|
|
26
33
|
// required rather than defaulted from `expo-constants` so this stays
|
|
27
34
|
// framework-agnostic; pass `Constants.expoConfig?.name`/`.version` from the
|
|
28
35
|
// caller.
|
|
29
|
-
export function AboutSettingsScreen({ appName, version, description, developerName, labels, styles, }) {
|
|
36
|
+
export function AboutSettingsScreen({ appName, version, description, sections, developerName, labels, styles, }) {
|
|
30
37
|
const merged = {
|
|
31
38
|
container: [defaultStyles.container, styles?.container],
|
|
32
39
|
appName: [defaultStyles.appName, styles?.appName],
|
|
33
40
|
version: [defaultStyles.version, styles?.version],
|
|
34
41
|
separator: [defaultStyles.separator, styles?.separator],
|
|
35
42
|
paragraph: [defaultStyles.paragraph, styles?.paragraph],
|
|
43
|
+
sectionTitle: [defaultStyles.sectionTitle, styles?.sectionTitle],
|
|
36
44
|
hint: [defaultStyles.hint, styles?.hint],
|
|
37
45
|
};
|
|
38
46
|
const t = { ...defaultLabels, ...labels };
|
|
39
47
|
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) })] }));
|
|
48
|
+
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))), sections?.map((section) => (_jsxs(View, { children: [_jsx(View, { style: merged.separator }), _jsx(Text, { style: merged.sectionTitle, children: section.title }), section.paragraphs.map((paragraph) => (_jsx(Text, { style: merged.paragraph, children: paragraph }, paragraph)))] }, section.title))), developerName && (_jsxs(_Fragment, { children: [_jsx(View, { style: merged.separator }), _jsx(Text, { style: merged.hint, children: t.developedBy(developerName) })] }))] }));
|
|
41
49
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { StyleProp, TextStyle, ViewStyle } from "react-native";
|
|
2
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
2
3
|
export interface BackupSettingsScreenReminder {
|
|
3
4
|
enabled: boolean;
|
|
4
5
|
/** Set while a permission request or the toggle's own persistence is in flight. */
|
|
@@ -7,13 +8,29 @@ export interface BackupSettingsScreenReminder {
|
|
|
7
8
|
/** How often the reminder fires, only used to word the hint (e.g. "every 7 days"). */
|
|
8
9
|
intervalDays: number;
|
|
9
10
|
}
|
|
11
|
+
export interface BackupSettingsScreenInfo {
|
|
12
|
+
label: string;
|
|
13
|
+
value: string;
|
|
14
|
+
}
|
|
15
|
+
export interface BackupSettingsScreenIcons {
|
|
16
|
+
export?: keyof typeof Ionicons.glyphMap;
|
|
17
|
+
import?: keyof typeof Ionicons.glyphMap;
|
|
18
|
+
}
|
|
10
19
|
export interface BackupSettingsScreenStyles {
|
|
11
20
|
container?: StyleProp<ViewStyle>;
|
|
21
|
+
infoBox?: StyleProp<ViewStyle>;
|
|
22
|
+
infoLabel?: StyleProp<TextStyle>;
|
|
23
|
+
infoValue?: StyleProp<TextStyle>;
|
|
24
|
+
sectionTitle?: StyleProp<TextStyle>;
|
|
12
25
|
hint?: StyleProp<TextStyle>;
|
|
13
26
|
row?: StyleProp<ViewStyle>;
|
|
14
27
|
button?: StyleProp<ViewStyle>;
|
|
15
28
|
buttonDisabled?: StyleProp<ViewStyle>;
|
|
16
29
|
buttonText?: StyleProp<TextStyle>;
|
|
30
|
+
iconColor?: string;
|
|
31
|
+
/** Merged on top of `button`/`buttonText` — defaults to the same look as the export button. */
|
|
32
|
+
importButton?: StyleProp<ViewStyle>;
|
|
33
|
+
importButtonText?: StyleProp<TextStyle>;
|
|
17
34
|
reminderRow?: StyleProp<ViewStyle>;
|
|
18
35
|
reminderTextColumn?: StyleProp<ViewStyle>;
|
|
19
36
|
reminderLabel?: StyleProp<TextStyle>;
|
|
@@ -24,6 +41,11 @@ export interface BackupSettingsScreenLabels {
|
|
|
24
41
|
hint?: string;
|
|
25
42
|
exportButton?: string;
|
|
26
43
|
importButton?: string;
|
|
44
|
+
/** Only shown in `layout="sections"`. */
|
|
45
|
+
exportSectionTitle?: string;
|
|
46
|
+
exportHelpText?: string;
|
|
47
|
+
importSectionTitle?: string;
|
|
48
|
+
importHelpText?: string;
|
|
27
49
|
importConfirmTitle?: string;
|
|
28
50
|
importConfirmMessage?: string;
|
|
29
51
|
importConfirmLabel?: string;
|
|
@@ -45,8 +67,18 @@ export interface BackupSettingsScreenProps {
|
|
|
45
67
|
onImport: () => Promise<void>;
|
|
46
68
|
/** Omit entirely to not show a reminder toggle at all. */
|
|
47
69
|
reminder?: BackupSettingsScreenReminder;
|
|
70
|
+
/** An info box above everything else, e.g. "Last auto-backup: ...". Omit to not show one. */
|
|
71
|
+
info?: BackupSettingsScreenInfo;
|
|
72
|
+
/**
|
|
73
|
+
* `"compact"` (default): one shared hint, both buttons side by side.
|
|
74
|
+
* `"sections"`: each action gets its own title + help text, buttons
|
|
75
|
+
* stacked full-width.
|
|
76
|
+
*/
|
|
77
|
+
layout?: "compact" | "sections";
|
|
78
|
+
/** Leading icon for each button. Omit either (or both) for a text-only button (default). */
|
|
79
|
+
icons?: BackupSettingsScreenIcons;
|
|
48
80
|
labels?: BackupSettingsScreenLabels;
|
|
49
81
|
styles?: BackupSettingsScreenStyles;
|
|
50
82
|
}
|
|
51
|
-
export declare function BackupSettingsScreen({ onExport, onImport, reminder, labels, styles, }: BackupSettingsScreenProps): import("react").JSX.Element;
|
|
83
|
+
export declare function BackupSettingsScreen({ onExport, onImport, reminder, info, layout, icons, labels, styles, }: BackupSettingsScreenProps): import("react").JSX.Element;
|
|
52
84
|
//# sourceMappingURL=BackupSettingsScreen.d.ts.map
|
|
@@ -1 +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;
|
|
1
|
+
{"version":3,"file":"BackupSettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/components/settings/BackupSettingsScreen.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,11 +1,16 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { useState } from "react";
|
|
3
3
|
import { ActivityIndicator, Alert, Pressable, Switch, Text, View, } from "react-native";
|
|
4
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
4
5
|
import { confirmDestructive } from "../../utils/helpers/confirmDestructive.js";
|
|
5
6
|
const defaultLabels = {
|
|
6
7
|
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
8
|
exportButton: "⬆️ Exporter",
|
|
8
9
|
importButton: "⬇️ Importer",
|
|
10
|
+
exportSectionTitle: "Exporter",
|
|
11
|
+
exportHelpText: "Exporte toutes tes données dans un fichier que tu peux garder précieusement ou transférer vers un autre appareil.",
|
|
12
|
+
importSectionTitle: "Importer",
|
|
13
|
+
importHelpText: "Restaure une sauvegarde exportée précédemment. Remplace entièrement les données actuelles — il n'y a pas de fusion.",
|
|
9
14
|
importConfirmTitle: "Importer une sauvegarde ?",
|
|
10
15
|
importConfirmMessage: "Toutes les données actuelles seront remplacées par celles du fichier choisi.",
|
|
11
16
|
importConfirmLabel: "Choisir un fichier",
|
|
@@ -18,9 +23,33 @@ const defaultLabels = {
|
|
|
18
23
|
};
|
|
19
24
|
const defaultStyles = {
|
|
20
25
|
container: {},
|
|
21
|
-
|
|
26
|
+
infoBox: {
|
|
27
|
+
backgroundColor: "#f9fafb",
|
|
28
|
+
borderWidth: 1,
|
|
29
|
+
borderColor: "#e5e7eb",
|
|
30
|
+
borderRadius: 10,
|
|
31
|
+
padding: 12,
|
|
32
|
+
marginBottom: 20,
|
|
33
|
+
gap: 2,
|
|
34
|
+
},
|
|
35
|
+
infoLabel: { fontSize: 12, color: "#6b7280" },
|
|
36
|
+
infoValue: { fontSize: 12, color: "#111827", fontWeight: "600" },
|
|
37
|
+
sectionTitle: {
|
|
38
|
+
fontSize: 15,
|
|
39
|
+
fontWeight: "700",
|
|
40
|
+
color: "#111827",
|
|
41
|
+
marginTop: 20,
|
|
42
|
+
},
|
|
43
|
+
hint: {
|
|
44
|
+
fontSize: 13,
|
|
45
|
+
color: "#6b7280",
|
|
46
|
+
marginTop: 6,
|
|
47
|
+
marginBottom: 14,
|
|
48
|
+
lineHeight: 18,
|
|
49
|
+
},
|
|
22
50
|
row: { flexDirection: "row", gap: 10 },
|
|
23
51
|
button: {
|
|
52
|
+
flexDirection: "row",
|
|
24
53
|
flex: 1,
|
|
25
54
|
paddingVertical: 12,
|
|
26
55
|
borderRadius: 8,
|
|
@@ -28,9 +57,13 @@ const defaultStyles = {
|
|
|
28
57
|
borderColor: "#2563eb",
|
|
29
58
|
alignItems: "center",
|
|
30
59
|
justifyContent: "center",
|
|
60
|
+
gap: 8,
|
|
31
61
|
},
|
|
32
62
|
buttonDisabled: { opacity: 0.5 },
|
|
33
63
|
buttonText: { color: "#2563eb", fontWeight: "700" },
|
|
64
|
+
iconColor: "#2563eb",
|
|
65
|
+
importButton: undefined,
|
|
66
|
+
importButtonText: undefined,
|
|
34
67
|
reminderRow: {
|
|
35
68
|
flexDirection: "row",
|
|
36
69
|
alignItems: "center",
|
|
@@ -55,16 +88,32 @@ function errorMessage(err, fallback) {
|
|
|
55
88
|
// what gets exported/imported is entirely app-specific (its own database
|
|
56
89
|
// tables), so `onExport`/`onImport` are required props, not something this
|
|
57
90
|
// package could have a sensible default for.
|
|
58
|
-
export function BackupSettingsScreen({ onExport, onImport, reminder, labels, styles, }) {
|
|
91
|
+
export function BackupSettingsScreen({ onExport, onImport, reminder, info, layout = "compact", icons, labels, styles, }) {
|
|
59
92
|
const [exporting, setExporting] = useState(false);
|
|
60
93
|
const [importing, setImporting] = useState(false);
|
|
61
94
|
const merged = {
|
|
62
95
|
container: [defaultStyles.container, styles?.container],
|
|
96
|
+
infoBox: [defaultStyles.infoBox, styles?.infoBox],
|
|
97
|
+
infoLabel: [defaultStyles.infoLabel, styles?.infoLabel],
|
|
98
|
+
infoValue: [defaultStyles.infoValue, styles?.infoValue],
|
|
99
|
+
sectionTitle: [defaultStyles.sectionTitle, styles?.sectionTitle],
|
|
63
100
|
hint: [defaultStyles.hint, styles?.hint],
|
|
64
101
|
row: [defaultStyles.row, styles?.row],
|
|
65
102
|
button: [defaultStyles.button, styles?.button],
|
|
66
103
|
buttonDisabled: [defaultStyles.buttonDisabled, styles?.buttonDisabled],
|
|
67
104
|
buttonText: [defaultStyles.buttonText, styles?.buttonText],
|
|
105
|
+
importButton: [
|
|
106
|
+
defaultStyles.button,
|
|
107
|
+
styles?.button,
|
|
108
|
+
defaultStyles.importButton,
|
|
109
|
+
styles?.importButton,
|
|
110
|
+
],
|
|
111
|
+
importButtonText: [
|
|
112
|
+
defaultStyles.buttonText,
|
|
113
|
+
styles?.buttonText,
|
|
114
|
+
defaultStyles.importButtonText,
|
|
115
|
+
styles?.importButtonText,
|
|
116
|
+
],
|
|
68
117
|
reminderRow: [defaultStyles.reminderRow, styles?.reminderRow],
|
|
69
118
|
reminderTextColumn: [
|
|
70
119
|
defaultStyles.reminderTextColumn,
|
|
@@ -75,6 +124,7 @@ export function BackupSettingsScreen({ onExport, onImport, reminder, labels, sty
|
|
|
75
124
|
};
|
|
76
125
|
const t = { ...defaultLabels, ...labels };
|
|
77
126
|
const activityIndicatorColor = styles?.activityIndicatorColor ?? defaultStyles.activityIndicatorColor;
|
|
127
|
+
const iconColor = styles?.iconColor ?? defaultStyles.iconColor;
|
|
78
128
|
async function handleExport() {
|
|
79
129
|
if (exporting)
|
|
80
130
|
return;
|
|
@@ -111,9 +161,11 @@ export function BackupSettingsScreen({ onExport, onImport, reminder, labels, sty
|
|
|
111
161
|
confirmLabel: t.importConfirmLabel,
|
|
112
162
|
});
|
|
113
163
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
164
|
+
const exportButton = (_jsx(Pressable, { style: [merged.button, exporting && merged.buttonDisabled], disabled: exporting, onPress: () => {
|
|
165
|
+
void handleExport();
|
|
166
|
+
}, accessibilityRole: "button", accessibilityLabel: t.exportButton, children: exporting ? (_jsx(ActivityIndicator, { color: activityIndicatorColor })) : (_jsxs(_Fragment, { children: [icons?.export && (_jsx(Ionicons, { name: icons.export, size: 18, color: iconColor })), _jsx(Text, { style: merged.buttonText, children: t.exportButton })] })) }));
|
|
167
|
+
const importButton = (_jsx(Pressable, { style: [merged.importButton, importing && merged.buttonDisabled], disabled: importing, onPress: handleImportPress, accessibilityRole: "button", accessibilityLabel: t.importButton, children: importing ? (_jsx(ActivityIndicator, { color: activityIndicatorColor })) : (_jsxs(_Fragment, { children: [icons?.import && (_jsx(Ionicons, { name: icons.import, size: 18, color: iconColor })), _jsx(Text, { style: merged.importButtonText, children: t.importButton })] })) }));
|
|
168
|
+
return (_jsxs(View, { style: merged.container, children: [info && (_jsxs(View, { style: merged.infoBox, children: [_jsx(Text, { style: merged.infoLabel, children: info.label }), _jsx(Text, { style: merged.infoValue, children: info.value })] })), layout === "sections" ? (_jsxs(_Fragment, { children: [_jsx(Text, { style: merged.sectionTitle, children: t.exportSectionTitle }), _jsx(Text, { style: merged.hint, children: t.exportHelpText }), exportButton, _jsx(Text, { style: merged.sectionTitle, children: t.importSectionTitle }), _jsx(Text, { style: merged.hint, children: t.importHelpText }), importButton] })) : (_jsxs(_Fragment, { children: [_jsx(Text, { style: merged.hint, children: t.hint }), _jsxs(View, { style: merged.row, children: [exportButton, importButton] })] })), reminder && (_jsxs(Pressable, { style: merged.reminderRow, disabled: reminder.busy, onPress: () => {
|
|
117
169
|
reminder.onToggle(!reminder.enabled);
|
|
118
170
|
}, 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
171
|
`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 }))] }))] }));
|
|
@@ -1,19 +1,40 @@
|
|
|
1
1
|
import type { StyleProp, TextStyle, ViewStyle } from "react-native";
|
|
2
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
3
|
+
export interface ContactSettingsScreenAction {
|
|
4
|
+
icon: keyof typeof Ionicons.glyphMap;
|
|
5
|
+
label: string;
|
|
6
|
+
onPress: () => void;
|
|
7
|
+
accessibilityLabel?: string;
|
|
8
|
+
}
|
|
2
9
|
export interface ContactSettingsScreenStyles {
|
|
3
10
|
container?: StyleProp<ViewStyle>;
|
|
4
11
|
hint?: StyleProp<TextStyle>;
|
|
5
12
|
emailButton?: StyleProp<ViewStyle>;
|
|
6
13
|
emailButtonText?: StyleProp<TextStyle>;
|
|
7
14
|
separator?: StyleProp<ViewStyle>;
|
|
15
|
+
/** Only used when `actions` is set (rows layout). */
|
|
16
|
+
row?: StyleProp<ViewStyle>;
|
|
17
|
+
rowText?: StyleProp<TextStyle>;
|
|
18
|
+
rowIconColor?: string;
|
|
8
19
|
}
|
|
9
20
|
export interface ContactSettingsScreenLabels {
|
|
10
21
|
hint?: string;
|
|
11
22
|
footer?: string;
|
|
23
|
+
/** Only used when `actions` is set — the email row's label (the button itself shows the address in the default single-button layout). */
|
|
24
|
+
emailRowLabel?: string;
|
|
12
25
|
}
|
|
13
26
|
export interface ContactSettingsScreenProps {
|
|
14
27
|
email: string;
|
|
28
|
+
/**
|
|
29
|
+
* Extra rows below the email one (icon + label). Setting this switches
|
|
30
|
+
* the whole section from a single bordered `mailto:` button to a list of
|
|
31
|
+
* icon rows — the email becomes the first row (icon defaults to
|
|
32
|
+
* `emailIcon`, label to `labels.emailRowLabel`) instead of the button.
|
|
33
|
+
*/
|
|
34
|
+
actions?: ContactSettingsScreenAction[];
|
|
35
|
+
emailIcon?: keyof typeof Ionicons.glyphMap;
|
|
15
36
|
labels?: ContactSettingsScreenLabels;
|
|
16
37
|
styles?: ContactSettingsScreenStyles;
|
|
17
38
|
}
|
|
18
|
-
export declare function ContactSettingsScreen({ email, labels, styles, }: ContactSettingsScreenProps): import("react").JSX.Element;
|
|
39
|
+
export declare function ContactSettingsScreen({ email, actions, emailIcon, labels, styles, }: ContactSettingsScreenProps): import("react").JSX.Element;
|
|
19
40
|
//# sourceMappingURL=ContactSettingsScreen.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ContactSettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/components/settings/ContactSettingsScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"ContactSettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/components/settings/ContactSettingsScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,MAAM,WAAW,2BAA2B;IAC1C,IAAI,EAAE,MAAM,OAAO,QAAQ,CAAC,QAAQ,CAAC;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,2BAA2B;IAC1C,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5B,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,eAAe,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACvC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,qDAAqD;IACrD,GAAG,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3B,OAAO,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,2BAA2B;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yIAAyI;IACzI,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,OAAO,CAAC,EAAE,2BAA2B,EAAE,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,OAAO,QAAQ,CAAC,QAAQ,CAAC;IAC3C,MAAM,CAAC,EAAE,2BAA2B,CAAC;IACrC,MAAM,CAAC,EAAE,2BAA2B,CAAC;CACtC;AA+CD,wBAAgB,qBAAqB,CAAC,EACpC,KAAK,EACL,OAAO,EACP,SAA0B,EAC1B,MAAM,EACN,MAAM,GACP,EAAE,0BAA0B,+BAkE5B"}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Linking, Pressable, Text, View } from "react-native";
|
|
3
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
3
4
|
const defaultLabels = {
|
|
4
5
|
hint: "Une question, un bug, une suggestion ?",
|
|
5
6
|
footer: "Si tu remontes un bug, précise si possible ce que tu faisais et ce que tu attendais — ça aide à le reproduire.",
|
|
7
|
+
emailRowLabel: "Me contacter",
|
|
6
8
|
};
|
|
7
9
|
const defaultStyles = {
|
|
8
10
|
container: {},
|
|
@@ -21,19 +23,40 @@ const defaultStyles = {
|
|
|
21
23
|
marginVertical: 16,
|
|
22
24
|
backgroundColor: "#e5e7eb",
|
|
23
25
|
},
|
|
26
|
+
row: {
|
|
27
|
+
flexDirection: "row",
|
|
28
|
+
alignItems: "center",
|
|
29
|
+
gap: 10,
|
|
30
|
+
backgroundColor: "#ffffff",
|
|
31
|
+
borderWidth: 1,
|
|
32
|
+
borderColor: "#e5e7eb",
|
|
33
|
+
borderRadius: 10,
|
|
34
|
+
padding: 14,
|
|
35
|
+
marginTop: 8,
|
|
36
|
+
},
|
|
37
|
+
rowText: { flex: 1, fontSize: 15, fontWeight: "600", color: "#111827" },
|
|
38
|
+
rowIconColor: "#111827",
|
|
24
39
|
};
|
|
25
40
|
// A mailto: button plus a short "how to report a bug" hint — the standard
|
|
26
|
-
// "Contact" section of a settings tab.
|
|
27
|
-
|
|
41
|
+
// "Contact" section of a settings tab. Pass `actions` for extra rows (share
|
|
42
|
+
// the app, a donation link...), switching email + those to a rows layout.
|
|
43
|
+
export function ContactSettingsScreen({ email, actions, emailIcon = "mail-outline", labels, styles, }) {
|
|
28
44
|
const merged = {
|
|
29
45
|
container: [defaultStyles.container, styles?.container],
|
|
30
46
|
hint: [defaultStyles.hint, styles?.hint],
|
|
31
47
|
emailButton: [defaultStyles.emailButton, styles?.emailButton],
|
|
32
48
|
emailButtonText: [defaultStyles.emailButtonText, styles?.emailButtonText],
|
|
33
49
|
separator: [defaultStyles.separator, styles?.separator],
|
|
50
|
+
row: [defaultStyles.row, styles?.row],
|
|
51
|
+
rowText: [defaultStyles.rowText, styles?.rowText],
|
|
34
52
|
};
|
|
35
53
|
const t = { ...defaultLabels, ...labels };
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
54
|
+
const rowIconColor = styles?.rowIconColor ?? defaultStyles.rowIconColor;
|
|
55
|
+
function openEmail() {
|
|
56
|
+
void Linking.openURL(`mailto:${email}`);
|
|
57
|
+
}
|
|
58
|
+
if (actions) {
|
|
59
|
+
return (_jsxs(View, { style: merged.container, children: [_jsx(Text, { style: merged.hint, children: t.hint }), _jsxs(Pressable, { style: merged.row, onPress: openEmail, accessibilityRole: "button", accessibilityLabel: `Envoyer un email à ${email}`, children: [_jsx(Ionicons, { name: emailIcon, size: 20, color: rowIconColor }), _jsx(Text, { style: merged.rowText, children: t.emailRowLabel })] }), actions.map((action) => (_jsxs(Pressable, { style: merged.row, onPress: action.onPress, accessibilityRole: "button", accessibilityLabel: action.accessibilityLabel ?? action.label, children: [_jsx(Ionicons, { name: action.icon, size: 20, color: rowIconColor }), _jsx(Text, { style: merged.rowText, children: action.label })] }, action.label)))] }));
|
|
60
|
+
}
|
|
61
|
+
return (_jsxs(View, { style: merged.container, children: [_jsx(Text, { style: merged.hint, children: t.hint }), _jsx(Pressable, { style: merged.emailButton, onPress: openEmail, accessibilityRole: "button", accessibilityLabel: `Envoyer un email à ${email}`, children: _jsx(Text, { style: merged.emailButtonText, children: `✉️ ${email}` }) }), _jsx(View, { style: merged.separator }), _jsx(Text, { style: merged.hint, children: t.footer })] }));
|
|
39
62
|
}
|
|
@@ -1,24 +1,64 @@
|
|
|
1
1
|
import type { StyleProp, TextStyle, ViewStyle } from "react-native";
|
|
2
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
3
|
+
/**
|
|
4
|
+
* Well-known settings sections this package itself ships a screen for
|
|
5
|
+
* (`BackupSettingsScreen`, `ThemeSettingsScreen`, etc.) — matching `key`
|
|
6
|
+
* against one of these gets an item a default icon for free. Any other
|
|
7
|
+
* `key` (e.g. an app-specific "profile"/"notifications" section) needs its
|
|
8
|
+
* own `emoji`/`icon`, since there's no sensible universal default for it.
|
|
9
|
+
*/
|
|
10
|
+
export type SettingsMenuSection = "backup" | "theme" | "update" | "about" | "contact" | "privacy";
|
|
2
11
|
export interface SettingsMenuItem {
|
|
3
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Stable React key. Doubles as the lookup into the default icon table
|
|
14
|
+
* above — use `"backup"`/`"theme"`/`"update"`/`"about"`/`"contact"`/
|
|
15
|
+
* `"privacy"` to get a sensible icon for free, any other string for a
|
|
16
|
+
* custom (app-specific) section, which then needs its own `emoji`/`icon`.
|
|
17
|
+
*/
|
|
4
18
|
key: string;
|
|
5
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Overrides the default icon for this item (whether that default came
|
|
21
|
+
* from `key` or there simply isn't one). At most one of the two —
|
|
22
|
+
* `emoji` renders as-is (naturally multicolor, varies per item); `icon`
|
|
23
|
+
* renders as an `Ionicons` glyph in one shared color (`styles.iconColor`),
|
|
24
|
+
* for a flatter, more uniform look.
|
|
25
|
+
*/
|
|
26
|
+
emoji?: string;
|
|
27
|
+
icon?: keyof typeof Ionicons.glyphMap;
|
|
6
28
|
title: string;
|
|
7
29
|
hint?: string;
|
|
8
30
|
onPress: () => void;
|
|
9
31
|
}
|
|
32
|
+
export interface SettingsMenuGroup {
|
|
33
|
+
/** Omit for a group with no visible header (e.g. the first one). */
|
|
34
|
+
title?: string;
|
|
35
|
+
items: SettingsMenuItem[];
|
|
36
|
+
}
|
|
10
37
|
export interface SettingsMenuStyles {
|
|
11
38
|
container?: StyleProp<ViewStyle>;
|
|
39
|
+
groupTitle?: StyleProp<TextStyle>;
|
|
12
40
|
row?: StyleProp<ViewStyle>;
|
|
13
41
|
titleColumn?: StyleProp<ViewStyle>;
|
|
14
42
|
emoji?: StyleProp<TextStyle>;
|
|
43
|
+
iconColor?: string;
|
|
15
44
|
title?: StyleProp<TextStyle>;
|
|
16
45
|
hint?: StyleProp<TextStyle>;
|
|
17
46
|
chevronColor?: string;
|
|
18
47
|
}
|
|
19
48
|
export interface SettingsMenuProps {
|
|
20
|
-
|
|
49
|
+
/** A flat list of rows. Mutually exclusive with `groups`. */
|
|
50
|
+
items?: SettingsMenuItem[];
|
|
51
|
+
/** Rows grouped under optional section headers. Mutually exclusive with `items`. */
|
|
52
|
+
groups?: SettingsMenuGroup[];
|
|
53
|
+
/**
|
|
54
|
+
* Which flavor of the default icon table to use for items that don't set
|
|
55
|
+
* their own `emoji`/`icon`. Doesn't affect items that do. Defaults to
|
|
56
|
+
* `"emoji"`.
|
|
57
|
+
*/
|
|
58
|
+
defaultIconKind?: "emoji" | "icon";
|
|
59
|
+
/** Set to `false` to never render any item's `hint`, for a denser, icon-only-style menu. Defaults to `true`. */
|
|
60
|
+
showHints?: boolean;
|
|
21
61
|
styles?: SettingsMenuStyles;
|
|
22
62
|
}
|
|
23
|
-
export declare function SettingsMenu({ items, styles }: SettingsMenuProps): import("react").JSX.Element;
|
|
63
|
+
export declare function SettingsMenu({ items, groups, defaultIconKind, showHints, styles, }: SettingsMenuProps): import("react").JSX.Element;
|
|
24
64
|
//# sourceMappingURL=SettingsMenu.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SettingsMenu.d.ts","sourceRoot":"","sources":["../../../src/components/settings/SettingsMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"SettingsMenu.d.ts","sourceRoot":"","sources":["../../../src/components/settings/SettingsMenu.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C;;;;;;GAMG;AACH,MAAM,MAAM,mBAAmB,GAC7B,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,CAAC;AAclE,MAAM,WAAW,gBAAgB;IAC/B;;;;;OAKG;IACH,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,OAAO,QAAQ,CAAC,QAAQ,CAAC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED,MAAM,WAAW,iBAAiB;IAChC,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,gBAAgB,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,UAAU,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAClC,GAAG,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3B,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,IAAI,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC5B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,iBAAiB;IAChC,6DAA6D;IAC7D,KAAK,CAAC,EAAE,gBAAgB,EAAE,CAAC;IAC3B,oFAAoF;IACpF,MAAM,CAAC,EAAE,iBAAiB,EAAE,CAAC;IAC7B;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACnC,gHAAgH;IAChH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,MAAM,CAAC,EAAE,kBAAkB,CAAC;CAC7B;AAmDD,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,MAAM,EACN,eAAyB,EACzB,SAAgB,EAChB,MAAM,GACP,EAAE,iBAAiB,+BAsDnB"}
|
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Pressable, Text, View } from "react-native";
|
|
3
3
|
import { Ionicons } from "@expo/vector-icons";
|
|
4
|
+
const defaultSectionIcons = {
|
|
5
|
+
backup: { emoji: "💾", icon: "save-outline" },
|
|
6
|
+
theme: { emoji: "🎨", icon: "contrast-outline" },
|
|
7
|
+
update: { emoji: "⬇️", icon: "download-outline" },
|
|
8
|
+
about: { emoji: "ℹ️", icon: "information-circle-outline" },
|
|
9
|
+
contact: { emoji: "✉️", icon: "chatbubble-ellipses-outline" },
|
|
10
|
+
privacy: { emoji: "🔒", icon: "lock-closed-outline" },
|
|
11
|
+
};
|
|
4
12
|
const defaultStyles = {
|
|
5
13
|
container: { gap: 10 },
|
|
14
|
+
groupTitle: {
|
|
15
|
+
fontSize: 13,
|
|
16
|
+
fontWeight: "700",
|
|
17
|
+
color: "#6b7280",
|
|
18
|
+
textTransform: "uppercase",
|
|
19
|
+
},
|
|
6
20
|
row: {
|
|
7
21
|
flexDirection: "row",
|
|
8
22
|
alignItems: "center",
|
|
@@ -14,6 +28,7 @@ const defaultStyles = {
|
|
|
14
28
|
},
|
|
15
29
|
titleColumn: { flex: 1 },
|
|
16
30
|
emoji: { fontSize: 24 },
|
|
31
|
+
iconColor: "#111827",
|
|
17
32
|
title: {
|
|
18
33
|
fontSize: 16,
|
|
19
34
|
fontWeight: "600",
|
|
@@ -22,13 +37,28 @@ const defaultStyles = {
|
|
|
22
37
|
hint: { fontSize: 12, color: "#6b7280", marginTop: 2 },
|
|
23
38
|
chevronColor: "#9ca3af",
|
|
24
39
|
};
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
40
|
+
function resolveIcon(item, defaultIconKind) {
|
|
41
|
+
if (item.icon)
|
|
42
|
+
return { icon: item.icon, emoji: undefined };
|
|
43
|
+
if (item.emoji)
|
|
44
|
+
return { icon: undefined, emoji: item.emoji };
|
|
45
|
+
const fallback = defaultSectionIcons[item.key];
|
|
46
|
+
if (!fallback)
|
|
47
|
+
return { icon: undefined, emoji: undefined };
|
|
48
|
+
return defaultIconKind === "icon"
|
|
49
|
+
? { icon: fallback.icon, emoji: undefined }
|
|
50
|
+
: { icon: undefined, emoji: fallback.emoji };
|
|
51
|
+
}
|
|
52
|
+
// Menu screen for a settings tab: one row per section (icon + title +
|
|
53
|
+
// optional hint), navigating to it on press, optionally grouped under
|
|
54
|
+
// section headers (`groups` instead of `items`). Has no opinion on how
|
|
55
|
+
// navigation works — each item carries its own `onPress` (e.g.
|
|
56
|
+
// `() => router.push("/settings/xxx")`), so this doesn't depend on
|
|
57
|
+
// expo-router or any other specific navigator.
|
|
58
|
+
export function SettingsMenu({ items, groups, defaultIconKind = "emoji", showHints = true, styles, }) {
|
|
30
59
|
const merged = {
|
|
31
60
|
container: [defaultStyles.container, styles?.container],
|
|
61
|
+
groupTitle: [defaultStyles.groupTitle, styles?.groupTitle],
|
|
32
62
|
row: [defaultStyles.row, styles?.row],
|
|
33
63
|
titleColumn: [defaultStyles.titleColumn, styles?.titleColumn],
|
|
34
64
|
emoji: [defaultStyles.emoji, styles?.emoji],
|
|
@@ -36,5 +66,14 @@ export function SettingsMenu({ items, styles }) {
|
|
|
36
66
|
hint: [defaultStyles.hint, styles?.hint],
|
|
37
67
|
};
|
|
38
68
|
const chevronColor = styles?.chevronColor ?? defaultStyles.chevronColor;
|
|
39
|
-
|
|
69
|
+
const iconColor = styles?.iconColor ?? defaultStyles.iconColor;
|
|
70
|
+
function renderItem(item) {
|
|
71
|
+
const { icon, emoji } = resolveIcon(item, defaultIconKind);
|
|
72
|
+
return (_jsxs(Pressable, { style: merged.row, onPress: item.onPress, accessibilityRole: "button", accessibilityLabel: item.title, children: [icon ? (_jsx(Ionicons, { name: icon, size: 24, color: iconColor })) : (_jsx(Text, { style: merged.emoji, children: emoji })), _jsxs(View, { style: merged.titleColumn, children: [_jsx(Text, { style: merged.title, children: item.title }), showHints && item.hint ? (_jsx(Text, { style: merged.hint, children: item.hint })) : null] }), _jsx(Ionicons, { name: "chevron-forward", size: 20, color: chevronColor })] }, item.key));
|
|
73
|
+
}
|
|
74
|
+
return (_jsx(View, { style: merged.container, children: groups
|
|
75
|
+
? groups.map((group, index) => (
|
|
76
|
+
// eslint-disable-next-line @eslint-react/no-array-index-key -- groups have no other stable identifier, and are a static, author-defined list (not reordered/filtered at runtime)
|
|
77
|
+
_jsxs(View, { style: merged.container, children: [group.title ? (_jsx(Text, { style: merged.groupTitle, children: group.title })) : null, group.items.map(renderItem)] }, group.title ?? index)))
|
|
78
|
+
: items?.map(renderItem) }));
|
|
40
79
|
}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { StyleProp, TextStyle, ViewStyle } from "react-native";
|
|
2
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
2
3
|
import type { ThemePreference } from "../../hooks/useEffectiveColorScheme.js";
|
|
3
4
|
export interface ThemeSettingsScreenEmojis {
|
|
4
5
|
light?: string;
|
|
5
6
|
dark?: string;
|
|
6
7
|
system?: string;
|
|
7
8
|
}
|
|
9
|
+
export interface ThemeSettingsScreenIcons {
|
|
10
|
+
light?: keyof typeof Ionicons.glyphMap;
|
|
11
|
+
dark?: keyof typeof Ionicons.glyphMap;
|
|
12
|
+
system?: keyof typeof Ionicons.glyphMap;
|
|
13
|
+
}
|
|
8
14
|
export interface ThemeSettingsScreenLabels {
|
|
9
15
|
light?: string;
|
|
10
16
|
dark?: string;
|
|
@@ -17,16 +23,29 @@ export interface ThemeSettingsScreenStyles {
|
|
|
17
23
|
row?: StyleProp<ViewStyle>;
|
|
18
24
|
rowActive?: StyleProp<ViewStyle>;
|
|
19
25
|
emoji?: StyleProp<TextStyle>;
|
|
26
|
+
iconColor?: string;
|
|
27
|
+
iconColorActive?: string;
|
|
20
28
|
label?: StyleProp<TextStyle>;
|
|
21
29
|
labelActive?: StyleProp<TextStyle>;
|
|
22
30
|
check?: StyleProp<TextStyle>;
|
|
31
|
+
checkColor?: string;
|
|
23
32
|
}
|
|
24
33
|
export interface ThemeSettingsScreenProps {
|
|
25
34
|
value: ThemePreference;
|
|
26
35
|
onChange: (preference: ThemePreference) => void;
|
|
36
|
+
/**
|
|
37
|
+
* `"emoji"` (default): colorful emoji + a plain-text `✓`. `"icon"`:
|
|
38
|
+
* `Ionicons` glyphs in one shared color (`styles.iconColor`) + an
|
|
39
|
+
* `Ionicons` checkmark, for a flatter, more uniform look — matches
|
|
40
|
+
* `SettingsMenu`'s `defaultIconKind`.
|
|
41
|
+
*/
|
|
42
|
+
variant?: "emoji" | "icon";
|
|
27
43
|
emojis?: ThemeSettingsScreenEmojis;
|
|
44
|
+
icons?: ThemeSettingsScreenIcons;
|
|
28
45
|
labels?: ThemeSettingsScreenLabels;
|
|
46
|
+
/** Set to `false` to hide the hint above the list. Defaults to `true`. */
|
|
47
|
+
showHint?: boolean;
|
|
29
48
|
styles?: ThemeSettingsScreenStyles;
|
|
30
49
|
}
|
|
31
|
-
export declare function ThemeSettingsScreen({ value, onChange, emojis, labels, styles, }: ThemeSettingsScreenProps): import("react").JSX.Element;
|
|
50
|
+
export declare function ThemeSettingsScreen({ value, onChange, variant, emojis, icons, labels, showHint, styles, }: ThemeSettingsScreenProps): import("react").JSX.Element;
|
|
32
51
|
//# sourceMappingURL=ThemeSettingsScreen.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ThemeSettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/components/settings/ThemeSettingsScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"ThemeSettingsScreen.d.ts","sourceRoot":"","sources":["../../../src/components/settings/ThemeSettingsScreen.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEpE,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAE9C,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wCAAwC,CAAC;AAE9E,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,CAAC,EAAE,MAAM,OAAO,QAAQ,CAAC,QAAQ,CAAC;IACvC,IAAI,CAAC,EAAE,MAAM,OAAO,QAAQ,CAAC,QAAQ,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,OAAO,QAAQ,CAAC,QAAQ,CAAC;CACzC;AAED,MAAM,WAAW,yBAAyB;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,yBAAyB;IACxC,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,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,eAAe,CAAC;IACvB,QAAQ,EAAE,CAAC,UAAU,EAAE,eAAe,KAAK,IAAI,CAAC;IAChD;;;;;OAKG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC3B,MAAM,CAAC,EAAE,yBAAyB,CAAC;IACnC,KAAK,CAAC,EAAE,wBAAwB,CAAC;IACjC,MAAM,CAAC,EAAE,yBAAyB,CAAC;IACnC,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,yBAAyB,CAAC;CACpC;AA8DD,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,EACR,OAAiB,EACjB,MAAM,EACN,KAAK,EACL,MAAM,EACN,QAAe,EACf,MAAM,GACP,EAAE,wBAAwB,+BAiE1B"}
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { Pressable, Text, View } from "react-native";
|
|
3
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
3
4
|
const OPTIONS = ["light", "dark", "system"];
|
|
4
5
|
const defaultEmojis = {
|
|
5
6
|
light: "☀️",
|
|
6
7
|
dark: "🌙",
|
|
7
8
|
system: "⚙️",
|
|
8
9
|
};
|
|
10
|
+
const defaultIcons = {
|
|
11
|
+
light: "sunny-outline",
|
|
12
|
+
dark: "moon-outline",
|
|
13
|
+
system: "phone-portrait-outline",
|
|
14
|
+
};
|
|
9
15
|
const defaultLabels = {
|
|
10
16
|
light: "Clair",
|
|
11
17
|
dark: "Sombre",
|
|
@@ -29,6 +35,8 @@ const defaultStyles = {
|
|
|
29
35
|
backgroundColor: "#2563eb1a",
|
|
30
36
|
},
|
|
31
37
|
emoji: { fontSize: 20 },
|
|
38
|
+
iconColor: "#111827",
|
|
39
|
+
iconColorActive: "#2563eb",
|
|
32
40
|
label: {
|
|
33
41
|
flex: 1,
|
|
34
42
|
fontSize: 15,
|
|
@@ -41,12 +49,14 @@ const defaultStyles = {
|
|
|
41
49
|
fontWeight: "700",
|
|
42
50
|
color: "#2563eb",
|
|
43
51
|
},
|
|
52
|
+
checkColor: "#2563eb",
|
|
44
53
|
};
|
|
45
|
-
// Selectable list of rows (
|
|
46
|
-
// theme preference — same data contract as `ThemeOptionList`, but
|
|
47
|
-
//
|
|
48
|
-
// your app; both cover the exact
|
|
49
|
-
|
|
54
|
+
// Selectable list of rows (icon + label + checkmark) for a light/dark/system
|
|
55
|
+
// theme preference — same data contract as `ThemeOptionList`, but with a
|
|
56
|
+
// second visual (`variant="emoji"`, the default) alongside the vector-icon
|
|
57
|
+
// one (`variant="icon"`). Pick whichever fits your app; both cover the exact
|
|
58
|
+
// same `light`/`dark`/`system` preference.
|
|
59
|
+
export function ThemeSettingsScreen({ value, onChange, variant = "emoji", emojis, icons, labels, showHint = true, styles, }) {
|
|
50
60
|
// Style fields are merged as arrays (default, then override) so a partial
|
|
51
61
|
// override only changes the properties it specifies instead of replacing
|
|
52
62
|
// the whole default style object.
|
|
@@ -61,11 +71,16 @@ export function ThemeSettingsScreen({ value, onChange, emojis, labels, styles, }
|
|
|
61
71
|
check: [defaultStyles.check, styles?.check],
|
|
62
72
|
};
|
|
63
73
|
const e = { ...defaultEmojis, ...emojis };
|
|
74
|
+
const i = { ...defaultIcons, ...icons };
|
|
64
75
|
const t = { ...defaultLabels, ...labels };
|
|
65
|
-
|
|
76
|
+
const iconColor = styles?.iconColor ?? defaultStyles.iconColor;
|
|
77
|
+
const iconColorActive = styles?.iconColorActive ?? defaultStyles.iconColorActive;
|
|
78
|
+
const checkColor = styles?.checkColor ?? defaultStyles.checkColor;
|
|
79
|
+
return (_jsxs(View, { style: merged.container, children: [showHint && _jsx(Text, { style: merged.hint, children: t.hint }), OPTIONS.map((option) => {
|
|
66
80
|
const active = option === value;
|
|
67
81
|
return (_jsxs(Pressable, { style: [merged.row, active && merged.rowActive], onPress: () => {
|
|
68
82
|
onChange(option);
|
|
69
|
-
}, accessibilityRole: "radio", accessibilityState: { checked: active }, accessibilityLabel: t[option], children: [_jsx(Text, { style: merged.emoji, children: e[option] }), _jsx(Text, { style: [merged.label, active && merged.labelActive], children: t[option] }), active &&
|
|
83
|
+
}, accessibilityRole: "radio", accessibilityState: { checked: active }, accessibilityLabel: t[option], children: [variant === "icon" ? (_jsx(Ionicons, { name: i[option], size: 20, color: active ? iconColorActive : iconColor })) : (_jsx(Text, { style: merged.emoji, children: e[option] })), _jsx(Text, { style: [merged.label, active && merged.labelActive], children: t[option] }), active &&
|
|
84
|
+
(variant === "icon" ? (_jsx(Ionicons, { name: "checkmark-circle", size: 20, color: checkColor })) : (_jsx(Text, { style: merged.check, children: "\u2713" })))] }, option));
|
|
70
85
|
})] }));
|
|
71
86
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forthtilliath/react-native-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"react-test-renderer": "19.2.3",
|
|
45
45
|
"typescript": "^5.9.3",
|
|
46
46
|
"vitest": "^4.1.10",
|
|
47
|
-
"@forthtilliath/
|
|
48
|
-
"@forthtilliath/
|
|
47
|
+
"@forthtilliath/eslint-config": "0.0.0",
|
|
48
|
+
"@forthtilliath/typescript-config": "0.0.0"
|
|
49
49
|
},
|
|
50
50
|
"scripts": {
|
|
51
51
|
"lint": "eslint . --max-warnings 0",
|