@forthtilliath/react-native-kit 0.12.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +603 -604
  3. package/dist/components/index.d.ts +0 -1
  4. package/dist/components/index.d.ts.map +1 -1
  5. package/dist/components/index.js +0 -1
  6. package/dist/components/list/ColorDot.d.ts +57 -0
  7. package/dist/components/list/ColorDot.d.ts.map +1 -0
  8. package/dist/components/list/ColorDot.js +48 -0
  9. package/dist/components/list/UndoToast.d.ts +100 -0
  10. package/dist/components/list/UndoToast.d.ts.map +1 -0
  11. package/dist/components/list/UndoToast.js +104 -0
  12. package/dist/components/list/index.d.ts +2 -0
  13. package/dist/components/list/index.d.ts.map +1 -1
  14. package/dist/components/list/index.js +2 -0
  15. package/dist/components/picker/PickerModal.d.ts +2 -29
  16. package/dist/components/picker/PickerModal.d.ts.map +1 -1
  17. package/dist/components/picker/PickerModal.js +10 -112
  18. package/dist/components/picker/PickerModal.styles.d.ts +32 -0
  19. package/dist/components/picker/PickerModal.styles.d.ts.map +1 -0
  20. package/dist/components/picker/PickerModal.styles.js +82 -0
  21. package/dist/components/picker/PickerModalRow.d.ts +17 -0
  22. package/dist/components/picker/PickerModalRow.d.ts.map +1 -0
  23. package/dist/components/picker/PickerModalRow.js +11 -0
  24. package/dist/components/picker/usePickerModalSections.d.ts +7 -0
  25. package/dist/components/picker/usePickerModalSections.d.ts.map +1 -0
  26. package/dist/components/picker/usePickerModalSections.js +28 -0
  27. package/dist/components/settings/UpdateSettingsScreen.d.ts +2 -46
  28. package/dist/components/settings/UpdateSettingsScreen.d.ts.map +1 -1
  29. package/dist/components/settings/UpdateSettingsScreen.js +4 -78
  30. package/dist/components/settings/UpdateSettingsScreen.styles.d.ts +49 -0
  31. package/dist/components/settings/UpdateSettingsScreen.styles.d.ts.map +1 -0
  32. package/dist/components/settings/UpdateSettingsScreen.styles.js +76 -0
  33. package/dist/components/settings/UpdateSettingsScreenHistory.d.ts +20 -0
  34. package/dist/components/settings/UpdateSettingsScreenHistory.d.ts.map +1 -0
  35. package/dist/components/settings/UpdateSettingsScreenHistory.js +10 -0
  36. package/dist/hooks/index.d.ts +0 -1
  37. package/dist/hooks/index.d.ts.map +1 -1
  38. package/dist/hooks/index.js +0 -1
  39. package/dist/index.d.ts +10 -11
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +12 -11
  42. package/dist/utils/helpers/index.d.ts +1 -6
  43. package/dist/utils/helpers/index.d.ts.map +1 -1
  44. package/dist/utils/helpers/index.js +1 -6
  45. package/dist/utils/index.d.ts +0 -1
  46. package/dist/utils/index.d.ts.map +1 -1
  47. package/dist/utils/index.js +0 -1
  48. package/package.json +4 -3
  49. package/dist/components/update/ChangelogNotes.d.ts +0 -22
  50. package/dist/components/update/ChangelogNotes.d.ts.map +0 -1
  51. package/dist/components/update/ChangelogNotes.js +0 -51
  52. package/dist/components/update/UpdateAvailableBanner.d.ts +0 -29
  53. package/dist/components/update/UpdateAvailableBanner.d.ts.map +0 -1
  54. package/dist/components/update/UpdateAvailableBanner.js +0 -63
  55. package/dist/components/update/index.d.ts +0 -3
  56. package/dist/components/update/index.d.ts.map +0 -1
  57. package/dist/components/update/index.js +0 -2
  58. package/dist/hooks/useUpdateCheck.d.ts +0 -35
  59. package/dist/hooks/useUpdateCheck.d.ts.map +0 -1
  60. package/dist/hooks/useUpdateCheck.js +0 -47
  61. package/dist/utils/format/escapeCsvField.d.ts +0 -2
  62. package/dist/utils/format/escapeCsvField.d.ts.map +0 -1
  63. package/dist/utils/format/escapeCsvField.js +0 -8
  64. package/dist/utils/format/escapeHtml.d.ts +0 -2
  65. package/dist/utils/format/escapeHtml.d.ts.map +0 -1
  66. package/dist/utils/format/escapeHtml.js +0 -9
  67. package/dist/utils/format/formatCsvNumber.d.ts +0 -2
  68. package/dist/utils/format/formatCsvNumber.d.ts.map +0 -1
  69. package/dist/utils/format/formatCsvNumber.js +0 -6
  70. package/dist/utils/format/index.d.ts +0 -4
  71. package/dist/utils/format/index.d.ts.map +0 -1
  72. package/dist/utils/format/index.js +0 -3
  73. package/dist/utils/helpers/getMostRecentIds.d.ts +0 -6
  74. package/dist/utils/helpers/getMostRecentIds.d.ts.map +0 -1
  75. package/dist/utils/helpers/getMostRecentIds.js +0 -19
  76. package/dist/utils/helpers/getPeriodStartMs.d.ts +0 -3
  77. package/dist/utils/helpers/getPeriodStartMs.d.ts.map +0 -1
  78. package/dist/utils/helpers/getPeriodStartMs.js +0 -14
  79. package/dist/utils/helpers/nextInCycle.d.ts +0 -2
  80. package/dist/utils/helpers/nextInCycle.d.ts.map +0 -1
  81. package/dist/utils/helpers/nextInCycle.js +0 -9
  82. package/dist/utils/helpers/normalizeForSearch.d.ts +0 -2
  83. package/dist/utils/helpers/normalizeForSearch.d.ts.map +0 -1
  84. package/dist/utils/helpers/normalizeForSearch.js +0 -15
  85. package/dist/utils/helpers/rankByNameMatch.d.ts +0 -2
  86. package/dist/utils/helpers/rankByNameMatch.d.ts.map +0 -1
  87. package/dist/utils/helpers/rankByNameMatch.js +0 -33
@@ -2,5 +2,4 @@ export * from "./list";
2
2
  export * from "./picker";
3
3
  export * from "./settings";
4
4
  export * from "./theme";
5
- export * from "./update";
6
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC;AACxB,cAAc,UAAU,CAAC"}
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"}
@@ -2,4 +2,3 @@ export * from "./list";
2
2
  export * from "./picker";
3
3
  export * from "./settings";
4
4
  export * from "./theme";
5
- export * from "./update";
@@ -0,0 +1,57 @@
1
+ import type { StyleProp, ViewStyle } from "react-native";
2
+ export interface ColorDotProps {
3
+ /**
4
+ * Dot color, as any React Native color string (`"#ef4444"`, `"tomato"`,
5
+ * `"rgba(0,0,0,0.5)"`…). When `undefined`, the component renders `null` —
6
+ * so an optional field can be passed straight through:
7
+ * `<ColorDot color={category.color} />`.
8
+ */
9
+ color?: string;
10
+ /**
11
+ * Diameter in px. The dot is always a perfect circle
12
+ * (`borderRadius = size / 2`).
13
+ *
14
+ * @defaultValue 10
15
+ */
16
+ size?: number;
17
+ /**
18
+ * Extra style merged after the computed `{ width, height, borderRadius,
19
+ * backgroundColor }`. Use it for spacing (`marginRight`), a border, or to
20
+ * override the shape.
21
+ */
22
+ style?: StyleProp<ViewStyle>;
23
+ }
24
+ /**
25
+ * A small round color swatch — the coloured bullet next to a category, a
26
+ * team, a tag, or a status.
27
+ *
28
+ * It renders **nothing** when `color` is `undefined`, which is the whole
29
+ * point: list rows can bind an optional color field without wrapping the dot
30
+ * in `{category.color && ...}` at every call site.
31
+ *
32
+ * @example Basic usage
33
+ * ```tsx
34
+ * import { ColorDot } from "@forthtilliath/react-native-kit/components/list/ColorDot";
35
+ *
36
+ * <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
37
+ * <ColorDot color="#ef4444" />
38
+ * <Text>Urgent</Text>
39
+ * </View>;
40
+ * ```
41
+ *
42
+ * @example Optional color (renders nothing when the category has none)
43
+ * ```tsx
44
+ * <ColorDot color={category.color} size={12} />
45
+ * ```
46
+ *
47
+ * @example Custom size and a subtle ring
48
+ * ```tsx
49
+ * <ColorDot
50
+ * color={team.color}
51
+ * size={16}
52
+ * style={{ borderWidth: 1, borderColor: "rgba(0,0,0,0.15)" }}
53
+ * />
54
+ * ```
55
+ */
56
+ export declare function ColorDot({ color, size, style }: ColorDotProps): import("react").JSX.Element | null;
57
+ //# sourceMappingURL=ColorDot.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ColorDot.d.ts","sourceRoot":"","sources":["../../../src/components/list/ColorDot.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGzD,MAAM,WAAW,aAAa;IAC5B;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;OAIG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC9B;AAID;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,QAAQ,CAAC,EAAE,KAAK,EAAE,IAAmB,EAAE,KAAK,EAAE,EAAE,aAAa,sCAe5E"}
@@ -0,0 +1,48 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { View } from "react-native";
3
+ const DEFAULT_SIZE = 10;
4
+ /**
5
+ * A small round color swatch — the coloured bullet next to a category, a
6
+ * team, a tag, or a status.
7
+ *
8
+ * It renders **nothing** when `color` is `undefined`, which is the whole
9
+ * point: list rows can bind an optional color field without wrapping the dot
10
+ * in `{category.color && ...}` at every call site.
11
+ *
12
+ * @example Basic usage
13
+ * ```tsx
14
+ * import { ColorDot } from "@forthtilliath/react-native-kit/components/list/ColorDot";
15
+ *
16
+ * <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
17
+ * <ColorDot color="#ef4444" />
18
+ * <Text>Urgent</Text>
19
+ * </View>;
20
+ * ```
21
+ *
22
+ * @example Optional color (renders nothing when the category has none)
23
+ * ```tsx
24
+ * <ColorDot color={category.color} size={12} />
25
+ * ```
26
+ *
27
+ * @example Custom size and a subtle ring
28
+ * ```tsx
29
+ * <ColorDot
30
+ * color={team.color}
31
+ * size={16}
32
+ * style={{ borderWidth: 1, borderColor: "rgba(0,0,0,0.15)" }}
33
+ * />
34
+ * ```
35
+ */
36
+ export function ColorDot({ color, size = DEFAULT_SIZE, style }) {
37
+ if (!color)
38
+ return null;
39
+ return (_jsx(View, { style: [
40
+ {
41
+ width: size,
42
+ height: size,
43
+ borderRadius: size / 2,
44
+ backgroundColor: color,
45
+ },
46
+ style,
47
+ ] }));
48
+ }
@@ -0,0 +1,100 @@
1
+ import type { StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ /**
3
+ * Per-slot style overrides for {@link UndoToast}. Every field is optional and
4
+ * is **merged after** the matching default (as a style array), so overriding
5
+ * one property (say `toast.backgroundColor`) keeps the rest of the default
6
+ * for that slot — the positioning, padding, and shadow don't disappear.
7
+ */
8
+ export interface UndoToastStyles {
9
+ /** The floating bar container (absolute-positioned, near the bottom). */
10
+ toast?: StyleProp<ViewStyle>;
11
+ /** The message text on the left. Truncated to a single line. */
12
+ message?: StyleProp<TextStyle>;
13
+ /** The action label on the right (the tappable "Undo"). */
14
+ action?: StyleProp<TextStyle>;
15
+ }
16
+ export interface UndoToastProps {
17
+ /**
18
+ * What just happened, phrased in the past tense — e.g.
19
+ * `"« Marie » supprimée"` or `"1 item deleted"`. Rendered on a
20
+ * single line and truncated with an ellipsis if too long.
21
+ */
22
+ message: string;
23
+ /**
24
+ * Label of the action button.
25
+ *
26
+ * @defaultValue "Undo"
27
+ */
28
+ actionLabel?: string;
29
+ /**
30
+ * Called when the action button is tapped. Wire this to your reverse-the-
31
+ * change logic (restore the row, re-insert the item…). The host is
32
+ * responsible for hiding the toast afterwards.
33
+ */
34
+ onAction: () => void;
35
+ /** Per-slot style overrides — see {@link UndoToastStyles}. */
36
+ styles?: UndoToastStyles;
37
+ }
38
+ /**
39
+ * A bottom-of-screen snackbar for **undoing an action that already
40
+ * happened** — the counterpart to a delete that runs immediately with no
41
+ * confirmation dialog (for instance a {@link SwipeToDeleteRow} swipe).
42
+ *
43
+ * `UndoToast` is presentational only: it does not time itself out, animate
44
+ * in/out, or stack. The host owns that — typically a piece of state holding
45
+ * the "pending undo" payload, a `setTimeout` that commits the change for
46
+ * good, and an exit animation. This keeps the component free of any
47
+ * animation-library or timer opinions.
48
+ *
49
+ * The default look is a dark floating bar, readable on both light and dark
50
+ * backgrounds. Re-theme it through {@link UndoToastProps.styles}.
51
+ *
52
+ * @example Minimal
53
+ * ```tsx
54
+ * import { UndoToast } from "@forthtilliath/react-native-kit/components/list/UndoToast";
55
+ *
56
+ * {recentlyDeleted && (
57
+ * <UndoToast
58
+ * message={`"${recentlyDeleted.name}" deleted`}
59
+ * onAction={restoreLast}
60
+ * />
61
+ * )}
62
+ * ```
63
+ *
64
+ * @example Full pattern — auto-commit after 5s, localized label, themed
65
+ * ```tsx
66
+ * const [pending, setPending] = useState<Employee | null>(null);
67
+ *
68
+ * function remove(employee: Employee) {
69
+ * setEmployees((list) => list.filter((e) => e.id !== employee.id));
70
+ * setPending(employee);
71
+ * }
72
+ *
73
+ * useEffect(() => {
74
+ * if (!pending) return;
75
+ * const id = setTimeout(() => {
76
+ * db.deleteEmployee(pending.id); // point of no return
77
+ * setPending(null);
78
+ * }, 5000);
79
+ * return () => clearTimeout(id);
80
+ * }, [pending]);
81
+ *
82
+ * {pending && (
83
+ * <UndoToast
84
+ * message={`« ${pending.name} » supprimé`}
85
+ * actionLabel="Annuler"
86
+ * onAction={() => {
87
+ * setEmployees((list) => [...list, pending]);
88
+ * setPending(null);
89
+ * }}
90
+ * styles={{
91
+ * toast: { backgroundColor: colors.inverseSurface },
92
+ * message: { color: colors.inverseOnSurface },
93
+ * action: { color: colors.inversePrimary },
94
+ * }}
95
+ * />
96
+ * )}
97
+ * ```
98
+ */
99
+ export declare function UndoToast({ message, actionLabel, onAction, styles, }: UndoToastProps): import("react").JSX.Element;
100
+ //# sourceMappingURL=UndoToast.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UndoToast.d.ts","sourceRoot":"","sources":["../../../src/components/list/UndoToast.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAGpE;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,yEAAyE;IACzE,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,gEAAgE;IAChE,OAAO,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/B,2DAA2D;IAC3D,MAAM,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CAC/B;AAED,MAAM,WAAW,cAAc;IAC7B;;;;OAIG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;OAIG;IACH,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,8DAA8D;IAC9D,MAAM,CAAC,EAAE,eAAe,CAAC;CAC1B;AAkCD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4DG;AACH,wBAAgB,SAAS,CAAC,EACxB,OAAO,EACP,WAAoB,EACpB,QAAQ,EACR,MAAM,GACP,EAAE,cAAc,+BAmBhB"}
@@ -0,0 +1,104 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Pressable, Text, View } from "react-native";
3
+ const defaultStyles = {
4
+ toast: {
5
+ position: "absolute",
6
+ left: 16,
7
+ right: 16,
8
+ bottom: 24,
9
+ flexDirection: "row",
10
+ alignItems: "center",
11
+ justifyContent: "space-between",
12
+ gap: 12,
13
+ paddingVertical: 14,
14
+ paddingHorizontal: 16,
15
+ borderRadius: 8,
16
+ backgroundColor: "#1f1f1f",
17
+ elevation: 4,
18
+ shadowColor: "#000000",
19
+ shadowOpacity: 0.3,
20
+ shadowRadius: 6,
21
+ shadowOffset: { width: 0, height: 2 },
22
+ },
23
+ message: {
24
+ flex: 1,
25
+ color: "#ffffff",
26
+ fontSize: 14,
27
+ },
28
+ action: {
29
+ color: "#7cc0ff",
30
+ fontWeight: "700",
31
+ fontSize: 14,
32
+ },
33
+ };
34
+ /**
35
+ * A bottom-of-screen snackbar for **undoing an action that already
36
+ * happened** — the counterpart to a delete that runs immediately with no
37
+ * confirmation dialog (for instance a {@link SwipeToDeleteRow} swipe).
38
+ *
39
+ * `UndoToast` is presentational only: it does not time itself out, animate
40
+ * in/out, or stack. The host owns that — typically a piece of state holding
41
+ * the "pending undo" payload, a `setTimeout` that commits the change for
42
+ * good, and an exit animation. This keeps the component free of any
43
+ * animation-library or timer opinions.
44
+ *
45
+ * The default look is a dark floating bar, readable on both light and dark
46
+ * backgrounds. Re-theme it through {@link UndoToastProps.styles}.
47
+ *
48
+ * @example Minimal
49
+ * ```tsx
50
+ * import { UndoToast } from "@forthtilliath/react-native-kit/components/list/UndoToast";
51
+ *
52
+ * {recentlyDeleted && (
53
+ * <UndoToast
54
+ * message={`"${recentlyDeleted.name}" deleted`}
55
+ * onAction={restoreLast}
56
+ * />
57
+ * )}
58
+ * ```
59
+ *
60
+ * @example Full pattern — auto-commit after 5s, localized label, themed
61
+ * ```tsx
62
+ * const [pending, setPending] = useState<Employee | null>(null);
63
+ *
64
+ * function remove(employee: Employee) {
65
+ * setEmployees((list) => list.filter((e) => e.id !== employee.id));
66
+ * setPending(employee);
67
+ * }
68
+ *
69
+ * useEffect(() => {
70
+ * if (!pending) return;
71
+ * const id = setTimeout(() => {
72
+ * db.deleteEmployee(pending.id); // point of no return
73
+ * setPending(null);
74
+ * }, 5000);
75
+ * return () => clearTimeout(id);
76
+ * }, [pending]);
77
+ *
78
+ * {pending && (
79
+ * <UndoToast
80
+ * message={`« ${pending.name} » supprimé`}
81
+ * actionLabel="Annuler"
82
+ * onAction={() => {
83
+ * setEmployees((list) => [...list, pending]);
84
+ * setPending(null);
85
+ * }}
86
+ * styles={{
87
+ * toast: { backgroundColor: colors.inverseSurface },
88
+ * message: { color: colors.inverseOnSurface },
89
+ * action: { color: colors.inversePrimary },
90
+ * }}
91
+ * />
92
+ * )}
93
+ * ```
94
+ */
95
+ export function UndoToast({ message, actionLabel = "Undo", onAction, styles, }) {
96
+ // Merge each slot as [default, override] so a partial override doesn't drop
97
+ // the rest of that slot's default (positioning, shadow, flex…).
98
+ const merged = {
99
+ toast: [defaultStyles.toast, styles?.toast],
100
+ message: [defaultStyles.message, styles?.message],
101
+ action: [defaultStyles.action, styles?.action],
102
+ };
103
+ return (_jsxs(View, { style: merged.toast, children: [_jsx(Text, { style: merged.message, numberOfLines: 1, children: message }), _jsx(Pressable, { onPress: onAction, hitSlop: 8, accessibilityRole: "button", children: _jsx(Text, { style: merged.action, children: actionLabel }) })] }));
104
+ }
@@ -1,3 +1,5 @@
1
+ export * from "./ColorDot";
1
2
  export * from "./SwipeableRow";
2
3
  export * from "./Thumbnail";
4
+ export * from "./UndoToast";
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/list/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/components/list/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,aAAa,CAAC;AAC5B,cAAc,aAAa,CAAC"}
@@ -1,2 +1,4 @@
1
+ export * from "./ColorDot";
1
2
  export * from "./SwipeableRow";
2
3
  export * from "./Thumbnail";
4
+ export * from "./UndoToast";
@@ -1,4 +1,5 @@
1
- import type { ImageStyle, StyleProp, TextStyle, ViewStyle } from "react-native";
1
+ import type { PickerModalLabels, PickerModalStyles } from "./PickerModal.styles.js";
2
+ export type { PickerModalLabels, PickerModalStyles, } from "./PickerModal.styles.js";
2
3
  export interface PickerItem {
3
4
  id: number;
4
5
  label: string;
@@ -11,34 +12,6 @@ export interface PickerItem {
11
12
  */
12
13
  group?: string;
13
14
  }
14
- export interface PickerModalStyles {
15
- container?: StyleProp<ViewStyle>;
16
- header?: StyleProp<ViewStyle>;
17
- title?: StyleProp<TextStyle>;
18
- close?: StyleProp<TextStyle>;
19
- searchRow?: StyleProp<ViewStyle>;
20
- search?: StyleProp<TextStyle>;
21
- row?: StyleProp<ViewStyle>;
22
- rowThumbnail?: StyleProp<ImageStyle>;
23
- rowThumbnailPlaceholder?: StyleProp<ViewStyle>;
24
- rowLabel?: StyleProp<TextStyle>;
25
- rowSubtitle?: StyleProp<TextStyle>;
26
- empty?: StyleProp<TextStyle>;
27
- sectionHeader?: StyleProp<TextStyle>;
28
- extraActions?: StyleProp<ViewStyle>;
29
- extraActionLabel?: StyleProp<TextStyle>;
30
- extraActionIconColor?: string;
31
- rowThumbnailPlaceholderIconColor?: string;
32
- placeholderTextColor?: string;
33
- }
34
- export interface PickerModalLabels {
35
- close?: string;
36
- searchPlaceholder?: string;
37
- searchAccessibilityLabel?: string;
38
- voiceSearchAccessibilityLabel?: string;
39
- defaultEmptyMessage?: string;
40
- otherGroupLabel?: string;
41
- }
42
15
  export interface PickerModalProps {
43
16
  visible: boolean;
44
17
  title: string;
@@ -1 +1 @@
1
- {"version":3,"file":"PickerModal.d.ts","sourceRoot":"","sources":["../../../src/components/picker/PickerModal.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PickerModal.d.ts","sourceRoot":"","sources":["../../../src/components/picker/PickerModal.tsx"],"names":[],"mappings":"AAWA,OAAO,KAAK,EACV,iBAAiB,EACjB,iBAAiB,EAClB,MAAM,yBAAyB,CAAC;AAMjC,YAAY,EACV,iBAAiB,EACjB,iBAAiB,GAClB,MAAM,yBAAyB,CAAC;AAEjC,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,UAAU,EAAE,CAAC;IACpB,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;IACrC,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,8FAA8F;IAC9F,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,KAAK,UAAU,EAAE,CAAC;IACnE,qFAAqF;IACrF,YAAY,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,IAAI,CAAA;KAAE,EAAE,CAAC;IACxD;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B;AAID,wBAAgB,WAAW,CAAC,EAC1B,OAAO,EACP,KAAK,EACL,KAAK,EACL,QAAQ,EACR,OAAO,EACP,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,UAAU,EACV,MAAM,EACN,MAAM,GACP,EAAE,gBAAgB,+BAwJlB"}
@@ -1,90 +1,11 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useMemo, useState } from "react";
3
- import { Image, Modal, Pressable, SectionList, Text, TextInput, View, } from "react-native";
3
+ import { Modal, Pressable, SectionList, Text, TextInput, View, } from "react-native";
4
4
  import { Ionicons } from "@expo/vector-icons";
5
+ import { defaultLabels, defaultStyles } from "./PickerModal.styles.js";
6
+ import { PickerModalRow } from "./PickerModalRow.js";
7
+ import { usePickerModalSections } from "./usePickerModalSections.js";
5
8
  import { VoiceSearchButton } from "./VoiceSearchButton.js";
6
- const defaultStyles = {
7
- container: {
8
- flex: 1,
9
- backgroundColor: "#ffffff",
10
- paddingTop: 60,
11
- paddingHorizontal: 16,
12
- },
13
- header: {
14
- flexDirection: "row",
15
- justifyContent: "space-between",
16
- alignItems: "center",
17
- marginBottom: 12,
18
- },
19
- title: { fontSize: 20, fontWeight: "700", color: "#111827" },
20
- close: { fontSize: 16, color: "#2563eb" },
21
- searchRow: {
22
- flexDirection: "row",
23
- alignItems: "center",
24
- gap: 10,
25
- marginBottom: 8,
26
- },
27
- search: {
28
- backgroundColor: "#f9fafb",
29
- borderWidth: 1,
30
- borderColor: "#e5e7eb",
31
- borderRadius: 10,
32
- paddingHorizontal: 12,
33
- paddingVertical: 10,
34
- fontSize: 16,
35
- color: "#111827",
36
- flex: 1,
37
- },
38
- row: {
39
- flexDirection: "row",
40
- alignItems: "center",
41
- gap: 12,
42
- backgroundColor: "#f9fafb",
43
- borderWidth: 1,
44
- borderColor: "#e5e7eb",
45
- borderRadius: 10,
46
- padding: 14,
47
- marginBottom: 8,
48
- },
49
- rowThumbnail: {
50
- width: 40,
51
- height: 40,
52
- borderRadius: 8,
53
- backgroundColor: "#ffffff",
54
- },
55
- rowThumbnailPlaceholder: {
56
- width: 40,
57
- height: 40,
58
- borderRadius: 8,
59
- backgroundColor: "#ffffff",
60
- alignItems: "center",
61
- justifyContent: "center",
62
- },
63
- rowLabel: { fontSize: 16, fontWeight: "600", color: "#111827" },
64
- rowSubtitle: { fontSize: 13, color: "#6b7280", marginTop: 2 },
65
- empty: { textAlign: "center", color: "#6b7280", marginTop: 24 },
66
- sectionHeader: {
67
- fontSize: 13,
68
- fontWeight: "700",
69
- color: "#6b7280",
70
- textTransform: "uppercase",
71
- marginTop: 8,
72
- marginBottom: 6,
73
- },
74
- extraActions: { marginBottom: 4 },
75
- extraActionLabel: { color: "#2563eb" },
76
- extraActionIconColor: "#2563eb",
77
- rowThumbnailPlaceholderIconColor: "#6b7280",
78
- placeholderTextColor: "#6b7280",
79
- };
80
- const defaultLabels = {
81
- close: "Fermer",
82
- searchPlaceholder: "Rechercher…",
83
- searchAccessibilityLabel: "Rechercher",
84
- voiceSearchAccessibilityLabel: "Dicter la recherche",
85
- defaultEmptyMessage: "Aucun résultat.",
86
- otherGroupLabel: "Autres",
87
- };
88
9
  // Full-screen picker: search (typed or dictated), optional sections, and
89
10
  // "add" actions always visible above the results.
90
11
  export function PickerModal({ visible, title, items, onSelect, onClose, emptyMessage, initialQuery, filterItems, extraActions, groupOrder, labels, styles, }) {
@@ -139,33 +60,10 @@ export function PickerModal({ visible, title, items, onSelect, onClose, emptyMes
139
60
  const q = query.trim().toLowerCase();
140
61
  return items.filter((item) => item.label.toLowerCase().includes(q));
141
62
  }, [items, query, filterItems, isSearching]);
142
- // Sections sorted by title: stable order, independent of the source
143
- // groups' order. No sections during a search (results ranked by relevance,
144
- // not by group), nor if no item has a group (uncategorized pickers,
145
- // unchanged behavior).
146
- const sections = useMemo(() => {
147
- if (isSearching || !filtered.some((item) => item.group)) {
148
- return [{ title: null, data: filtered }];
149
- }
150
- const byGroup = new Map();
151
- for (const item of filtered) {
152
- const key = item.group ?? t.otherGroupLabel;
153
- const group = byGroup.get(key);
154
- if (group)
155
- group.push(item);
156
- else
157
- byGroup.set(key, [item]);
158
- }
159
- function sortIndex(groupTitle) {
160
- const index = groupOrder?.indexOf(groupTitle) ?? -1;
161
- return index === -1 ? (groupOrder?.length ?? 0) : index;
162
- }
163
- return [...byGroup.entries()]
164
- .sort(([a], [b]) => sortIndex(a) - sortIndex(b) || a.localeCompare(b))
165
- .map(([sectionTitle, data]) => ({ title: sectionTitle, data }));
166
- }, [filtered, isSearching, groupOrder, t.otherGroupLabel]);
167
- return (_jsx(Modal, { visible: visible, animationType: "slide", onRequestClose: onClose, children: _jsxs(View, { style: merged.container, children: [_jsxs(View, { style: merged.header, children: [_jsx(Text, { style: merged.title, children: title }), _jsx(Pressable, { onPress: onClose, hitSlop: 12, accessibilityRole: "button", accessibilityLabel: t.close, children: _jsx(Text, { style: merged.close, children: t.close }) })] }), _jsxs(View, { style: merged.searchRow, children: [_jsx(TextInput, { style: merged.search, placeholder: t.searchPlaceholder, placeholderTextColor: merged.placeholderTextColor, value: query, onChangeText: setQuery, autoFocus: true, accessibilityLabel: t.searchAccessibilityLabel }), _jsx(VoiceSearchButton, { onResult: setQuery, accessibilityLabel: t.voiceSearchAccessibilityLabel })] }), _jsx(SectionList, { sections: sections, keyExtractor: (item) => String(item.id), keyboardShouldPersistTaps: "handled", stickySectionHeadersEnabled: false, ListHeaderComponent: extraActions && extraActions.length > 0 ? (_jsx(View, { style: merged.extraActions, children: extraActions.map((action) => (_jsxs(Pressable, { style: merged.row, onPress: action.onPress, accessibilityRole: "button", accessibilityLabel: action.label, children: [_jsx(Ionicons, { name: "add-circle-outline", size: 20, color: merged.extraActionIconColor }), _jsx(Text, { style: [merged.rowLabel, merged.extraActionLabel], children: action.label })] }, action.label))) })) : null, ListEmptyComponent: _jsx(Text, { style: merged.empty, children: emptyMessage ?? t.defaultEmptyMessage }), renderSectionHeader: ({ section: { title: sectionTitle }, }) => sectionTitle ? (_jsx(Text, { style: merged.sectionHeader, children: sectionTitle })) : null, renderItem: ({ item }) => (_jsxs(Pressable, { style: merged.row, onPress: () => {
168
- onSelect(item);
169
- setQuery("");
170
- }, accessibilityRole: "button", accessibilityLabel: item.subtitle ? `${item.label}, ${item.subtitle}` : item.label, children: [item.imageUri ? (_jsx(Image, { source: { uri: item.imageUri }, style: merged.rowThumbnail, accessibilityIgnoresInvertColors: true })) : item.imageUri === null ? (_jsx(View, { style: merged.rowThumbnailPlaceholder, children: _jsx(Ionicons, { name: "cube-outline", size: 18, color: merged.rowThumbnailPlaceholderIconColor }) })) : null, _jsxs(View, { style: { flex: 1 }, children: [_jsx(Text, { style: merged.rowLabel, children: item.label }), item.subtitle ? (_jsx(Text, { style: merged.rowSubtitle, children: item.subtitle })) : null] })] })) })] }) }));
63
+ const sections = usePickerModalSections(filtered, isSearching, groupOrder, t.otherGroupLabel);
64
+ function handleSelect(item) {
65
+ onSelect(item);
66
+ setQuery("");
67
+ }
68
+ return (_jsx(Modal, { visible: visible, animationType: "slide", onRequestClose: onClose, children: _jsxs(View, { style: merged.container, children: [_jsxs(View, { style: merged.header, children: [_jsx(Text, { style: merged.title, children: title }), _jsx(Pressable, { onPress: onClose, hitSlop: 12, accessibilityRole: "button", accessibilityLabel: t.close, children: _jsx(Text, { style: merged.close, children: t.close }) })] }), _jsxs(View, { style: merged.searchRow, children: [_jsx(TextInput, { style: merged.search, placeholder: t.searchPlaceholder, placeholderTextColor: merged.placeholderTextColor, value: query, onChangeText: setQuery, autoFocus: true, accessibilityLabel: t.searchAccessibilityLabel }), _jsx(VoiceSearchButton, { onResult: setQuery, accessibilityLabel: t.voiceSearchAccessibilityLabel })] }), _jsx(SectionList, { sections: sections, keyExtractor: (item) => String(item.id), keyboardShouldPersistTaps: "handled", stickySectionHeadersEnabled: false, ListHeaderComponent: extraActions && extraActions.length > 0 ? (_jsx(View, { style: merged.extraActions, children: extraActions.map((action) => (_jsxs(Pressable, { style: merged.row, onPress: action.onPress, accessibilityRole: "button", accessibilityLabel: action.label, children: [_jsx(Ionicons, { name: "add-circle-outline", size: 20, color: merged.extraActionIconColor }), _jsx(Text, { style: [merged.rowLabel, merged.extraActionLabel], children: action.label })] }, action.label))) })) : null, ListEmptyComponent: _jsx(Text, { style: merged.empty, children: emptyMessage ?? t.defaultEmptyMessage }), renderSectionHeader: ({ section: { title: sectionTitle }, }) => sectionTitle ? (_jsx(Text, { style: merged.sectionHeader, children: sectionTitle })) : null, renderItem: ({ item }) => (_jsx(PickerModalRow, { item: item, styles: merged, onSelect: handleSelect })) })] }) }));
171
69
  }
@@ -0,0 +1,32 @@
1
+ import type { ImageStyle, StyleProp, TextStyle, ViewStyle } from "react-native";
2
+ export interface PickerModalStyles {
3
+ container?: StyleProp<ViewStyle>;
4
+ header?: StyleProp<ViewStyle>;
5
+ title?: StyleProp<TextStyle>;
6
+ close?: StyleProp<TextStyle>;
7
+ searchRow?: StyleProp<ViewStyle>;
8
+ search?: StyleProp<TextStyle>;
9
+ row?: StyleProp<ViewStyle>;
10
+ rowThumbnail?: StyleProp<ImageStyle>;
11
+ rowThumbnailPlaceholder?: StyleProp<ViewStyle>;
12
+ rowLabel?: StyleProp<TextStyle>;
13
+ rowSubtitle?: StyleProp<TextStyle>;
14
+ empty?: StyleProp<TextStyle>;
15
+ sectionHeader?: StyleProp<TextStyle>;
16
+ extraActions?: StyleProp<ViewStyle>;
17
+ extraActionLabel?: StyleProp<TextStyle>;
18
+ extraActionIconColor?: string;
19
+ rowThumbnailPlaceholderIconColor?: string;
20
+ placeholderTextColor?: string;
21
+ }
22
+ export interface PickerModalLabels {
23
+ close?: string;
24
+ searchPlaceholder?: string;
25
+ searchAccessibilityLabel?: string;
26
+ voiceSearchAccessibilityLabel?: string;
27
+ defaultEmptyMessage?: string;
28
+ otherGroupLabel?: string;
29
+ }
30
+ export declare const defaultStyles: Required<PickerModalStyles>;
31
+ export declare const defaultLabels: Required<PickerModalLabels>;
32
+ //# sourceMappingURL=PickerModal.styles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PickerModal.styles.d.ts","sourceRoot":"","sources":["../../../src/components/picker/PickerModal.styles.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAEhF,MAAM,WAAW,iBAAiB;IAChC,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC9B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,SAAS,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACjC,MAAM,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC9B,GAAG,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC3B,YAAY,CAAC,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IACrC,uBAAuB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/C,QAAQ,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAChC,WAAW,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACnC,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC7B,aAAa,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACrC,YAAY,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACpC,gBAAgB,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IACxC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gCAAgC,CAAC,EAAE,MAAM,CAAC;IAC1C,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,6BAA6B,CAAC,EAAE,MAAM,CAAC;IACvC,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,iBAAiB,CAyErD,CAAC;AAEF,eAAO,MAAM,aAAa,EAAE,QAAQ,CAAC,iBAAiB,CAOrD,CAAC"}
@@ -0,0 +1,82 @@
1
+ export const defaultStyles = {
2
+ container: {
3
+ flex: 1,
4
+ backgroundColor: "#ffffff",
5
+ paddingTop: 60,
6
+ paddingHorizontal: 16,
7
+ },
8
+ header: {
9
+ flexDirection: "row",
10
+ justifyContent: "space-between",
11
+ alignItems: "center",
12
+ marginBottom: 12,
13
+ },
14
+ title: { fontSize: 20, fontWeight: "700", color: "#111827" },
15
+ close: { fontSize: 16, color: "#2563eb" },
16
+ searchRow: {
17
+ flexDirection: "row",
18
+ alignItems: "center",
19
+ gap: 10,
20
+ marginBottom: 8,
21
+ },
22
+ search: {
23
+ backgroundColor: "#f9fafb",
24
+ borderWidth: 1,
25
+ borderColor: "#e5e7eb",
26
+ borderRadius: 10,
27
+ paddingHorizontal: 12,
28
+ paddingVertical: 10,
29
+ fontSize: 16,
30
+ color: "#111827",
31
+ flex: 1,
32
+ },
33
+ row: {
34
+ flexDirection: "row",
35
+ alignItems: "center",
36
+ gap: 12,
37
+ backgroundColor: "#f9fafb",
38
+ borderWidth: 1,
39
+ borderColor: "#e5e7eb",
40
+ borderRadius: 10,
41
+ padding: 14,
42
+ marginBottom: 8,
43
+ },
44
+ rowThumbnail: {
45
+ width: 40,
46
+ height: 40,
47
+ borderRadius: 8,
48
+ backgroundColor: "#ffffff",
49
+ },
50
+ rowThumbnailPlaceholder: {
51
+ width: 40,
52
+ height: 40,
53
+ borderRadius: 8,
54
+ backgroundColor: "#ffffff",
55
+ alignItems: "center",
56
+ justifyContent: "center",
57
+ },
58
+ rowLabel: { fontSize: 16, fontWeight: "600", color: "#111827" },
59
+ rowSubtitle: { fontSize: 13, color: "#6b7280", marginTop: 2 },
60
+ empty: { textAlign: "center", color: "#6b7280", marginTop: 24 },
61
+ sectionHeader: {
62
+ fontSize: 13,
63
+ fontWeight: "700",
64
+ color: "#6b7280",
65
+ textTransform: "uppercase",
66
+ marginTop: 8,
67
+ marginBottom: 6,
68
+ },
69
+ extraActions: { marginBottom: 4 },
70
+ extraActionLabel: { color: "#2563eb" },
71
+ extraActionIconColor: "#2563eb",
72
+ rowThumbnailPlaceholderIconColor: "#6b7280",
73
+ placeholderTextColor: "#6b7280",
74
+ };
75
+ export const defaultLabels = {
76
+ close: "Fermer",
77
+ searchPlaceholder: "Rechercher…",
78
+ searchAccessibilityLabel: "Rechercher",
79
+ voiceSearchAccessibilityLabel: "Dicter la recherche",
80
+ defaultEmptyMessage: "Aucun résultat.",
81
+ otherGroupLabel: "Autres",
82
+ };