@forthtilliath/react-native-kit 0.13.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.
- package/README.md +18 -116
- package/dist/components/index.d.ts +0 -1
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +0 -1
- package/dist/components/picker/PickerModal.d.ts +2 -29
- package/dist/components/picker/PickerModal.d.ts.map +1 -1
- package/dist/components/picker/PickerModal.js +10 -112
- package/dist/components/picker/PickerModal.styles.d.ts +32 -0
- package/dist/components/picker/PickerModal.styles.d.ts.map +1 -0
- package/dist/components/picker/PickerModal.styles.js +82 -0
- package/dist/components/picker/PickerModalRow.d.ts +17 -0
- package/dist/components/picker/PickerModalRow.d.ts.map +1 -0
- package/dist/components/picker/PickerModalRow.js +11 -0
- package/dist/components/picker/usePickerModalSections.d.ts +7 -0
- package/dist/components/picker/usePickerModalSections.d.ts.map +1 -0
- package/dist/components/picker/usePickerModalSections.js +28 -0
- package/dist/components/settings/UpdateSettingsScreen.d.ts +2 -46
- package/dist/components/settings/UpdateSettingsScreen.d.ts.map +1 -1
- package/dist/components/settings/UpdateSettingsScreen.js +4 -78
- package/dist/components/settings/UpdateSettingsScreen.styles.d.ts +49 -0
- package/dist/components/settings/UpdateSettingsScreen.styles.d.ts.map +1 -0
- package/dist/components/settings/UpdateSettingsScreen.styles.js +76 -0
- package/dist/components/settings/UpdateSettingsScreenHistory.d.ts +20 -0
- package/dist/components/settings/UpdateSettingsScreenHistory.d.ts.map +1 -0
- package/dist/components/settings/UpdateSettingsScreenHistory.js +10 -0
- package/dist/hooks/index.d.ts +0 -1
- package/dist/hooks/index.d.ts.map +1 -1
- package/dist/hooks/index.js +0 -1
- package/dist/index.d.ts +8 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -11
- package/dist/utils/helpers/index.d.ts +1 -6
- package/dist/utils/helpers/index.d.ts.map +1 -1
- package/dist/utils/helpers/index.js +1 -6
- package/dist/utils/index.d.ts +0 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +0 -1
- package/package.json +4 -3
- package/dist/components/update/ChangelogNotes.d.ts +0 -22
- package/dist/components/update/ChangelogNotes.d.ts.map +0 -1
- package/dist/components/update/ChangelogNotes.js +0 -51
- package/dist/components/update/UpdateAvailableBanner.d.ts +0 -29
- package/dist/components/update/UpdateAvailableBanner.d.ts.map +0 -1
- package/dist/components/update/UpdateAvailableBanner.js +0 -63
- package/dist/components/update/index.d.ts +0 -3
- package/dist/components/update/index.d.ts.map +0 -1
- package/dist/components/update/index.js +0 -2
- package/dist/hooks/useUpdateCheck.d.ts +0 -35
- package/dist/hooks/useUpdateCheck.d.ts.map +0 -1
- package/dist/hooks/useUpdateCheck.js +0 -47
- package/dist/utils/format/escapeCsvField.d.ts +0 -2
- package/dist/utils/format/escapeCsvField.d.ts.map +0 -1
- package/dist/utils/format/escapeCsvField.js +0 -8
- package/dist/utils/format/escapeHtml.d.ts +0 -2
- package/dist/utils/format/escapeHtml.d.ts.map +0 -1
- package/dist/utils/format/escapeHtml.js +0 -9
- package/dist/utils/format/formatCsvNumber.d.ts +0 -2
- package/dist/utils/format/formatCsvNumber.d.ts.map +0 -1
- package/dist/utils/format/formatCsvNumber.js +0 -6
- package/dist/utils/format/index.d.ts +0 -4
- package/dist/utils/format/index.d.ts.map +0 -1
- package/dist/utils/format/index.js +0 -3
- package/dist/utils/helpers/getMostRecentIds.d.ts +0 -6
- package/dist/utils/helpers/getMostRecentIds.d.ts.map +0 -1
- package/dist/utils/helpers/getMostRecentIds.js +0 -19
- package/dist/utils/helpers/getPeriodStartMs.d.ts +0 -3
- package/dist/utils/helpers/getPeriodStartMs.d.ts.map +0 -1
- package/dist/utils/helpers/getPeriodStartMs.js +0 -14
- package/dist/utils/helpers/nextInCycle.d.ts +0 -2
- package/dist/utils/helpers/nextInCycle.d.ts.map +0 -1
- package/dist/utils/helpers/nextInCycle.js +0 -9
- package/dist/utils/helpers/normalizeForSearch.d.ts +0 -2
- package/dist/utils/helpers/normalizeForSearch.d.ts.map +0 -1
- package/dist/utils/helpers/normalizeForSearch.js +0 -15
- package/dist/utils/helpers/rankByNameMatch.d.ts +0 -2
- package/dist/utils/helpers/rankByNameMatch.d.ts.map +0 -1
- package/dist/utils/helpers/rankByNameMatch.js +0 -33
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
|
|
3
|
+
Small React Native building blocks — components, hooks, and a framework-agnostic util — with no opinion on your app's theme, distributed one file per export (grouped under `components/{picker,theme,list,settings}/`, `hooks/`, `utils/helpers/`) so consumers only pull in what they use. Update-checking UI (`ChangelogNotes`, `UpdateAvailableBanner`, `useUpdateCheck`) lives in the sibling `@forthtilliath/expo-release-updates-ui` package instead.
|
|
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 {
|
|
28
|
+
import { PhotoPicker } from "@forthtilliath/react-native-kit/components/picker/PhotoPicker";
|
|
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.
|
|
@@ -33,56 +33,11 @@ This is the recommended way to import: Metro (React Native's bundler) doesn't re
|
|
|
33
33
|
A root barrel is also available for convenience:
|
|
34
34
|
|
|
35
35
|
```ts
|
|
36
|
-
import {
|
|
37
|
-
ChangelogNotes,
|
|
38
|
-
useSubmitGuard,
|
|
39
|
-
} from "@forthtilliath/react-native-kit";
|
|
36
|
+
import { PhotoPicker, useSubmitGuard } from "@forthtilliath/react-native-kit";
|
|
40
37
|
```
|
|
41
38
|
|
|
42
39
|
**Avoid the barrel under Jest (or any other CommonJS `require` consumer).** `export * from` re-exports are evaluated eagerly on `require()` — unlike Metro's ESM bundling, there's no tree-shaking to skip the unused ones. Requiring the barrel from _any_ file, even one that only wants a framework-agnostic util like `getMostRecentIds`, pulls in every component's module graph, including native-module imports (`expo-speech-recognition` via `VoiceSearchButton`/`PickerModal`) that don't exist in a Jest environment — this throws `Cannot find native module '...'` at require time, not just at runtime for an unrendered component. Deep imports only ever load the one module you asked for, so they don't have this problem in any environment.
|
|
43
40
|
|
|
44
|
-
### `<ChangelogNotes notes={...} styles={...} />`
|
|
45
|
-
|
|
46
|
-
Renders GitHub-style release notes — as produced by `@forthtilliath/expo-release-updates`'s `parseChangelogNotes` — as headings, bulleted items, and bold-aware text, instead of showing the raw Markdown syntax in a plain `Text`.
|
|
47
|
-
|
|
48
|
-
```tsx
|
|
49
|
-
<ChangelogNotes notes={release.notes} />
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
It understands the small subset of Markdown GitHub release notes actually use:
|
|
53
|
-
|
|
54
|
-
| `notes` input | Rendered as |
|
|
55
|
-
| ----------------------------------------- | --------------------------------------------------------------------------- |
|
|
56
|
-
| `### Added` | a heading `Text` — `"Added"` |
|
|
57
|
-
| `- Export history to CSV.` | a row (`View`) with a `•` bullet `Text` and an item `Text` |
|
|
58
|
-
| `**Auto-backup**: saves every 5 minutes.` | a plain `Text`, with the bold segment split into its own bold-styled `Text` |
|
|
59
|
-
|
|
60
|
-
A full example, mixing all three:
|
|
61
|
-
|
|
62
|
-
```tsx
|
|
63
|
-
<ChangelogNotes
|
|
64
|
-
notes={`### Added\n- **Auto-backup**: saves every 5 minutes.\n- Export history to CSV.\n\nSee the full changelog for details.`}
|
|
65
|
-
/>
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
The component ships with neutral default styles and no opinion on your app's theme. Override any subset via the `styles` prop to match your colors/dark mode:
|
|
69
|
-
|
|
70
|
-
```tsx
|
|
71
|
-
<ChangelogNotes
|
|
72
|
-
notes={release.notes}
|
|
73
|
-
styles={{
|
|
74
|
-
heading: { color: colors.text, fontWeight: "700" },
|
|
75
|
-
itemRow: { gap: 8 },
|
|
76
|
-
bullet: { color: colors.primary },
|
|
77
|
-
itemText: { color: colors.textMuted },
|
|
78
|
-
text: { color: colors.textMuted },
|
|
79
|
-
bold: { color: colors.text, fontWeight: "700" },
|
|
80
|
-
}}
|
|
81
|
-
/>
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Every field of `styles` is optional — pass only the ones you want to override; the rest fall back to the defaults (`ChangelogNotesStyles` in `ChangelogNotes.tsx`).
|
|
85
|
-
|
|
86
41
|
### `<Thumbnail photoUri={...} placeholderIcon="..." />`
|
|
87
42
|
|
|
88
43
|
List-row thumbnail: the photo if there is one, otherwise a placeholder icon.
|
|
@@ -345,51 +300,9 @@ import { ThemeOptionList } from "@forthtilliath/react-native-kit/components/them
|
|
|
345
300
|
<ThemeOptionList value={themePreference} onChange={setThemePreference} />;
|
|
346
301
|
```
|
|
347
302
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
```tsx
|
|
353
|
-
import { useUpdateCheck } from "@forthtilliath/react-native-kit/hooks/useUpdateCheck";
|
|
354
|
-
|
|
355
|
-
const update = useUpdateCheck({
|
|
356
|
-
currentVersion: Constants.expoConfig?.version ?? "0.0.0",
|
|
357
|
-
checkForUpdate: fetchLatestRelease,
|
|
358
|
-
compareVersions,
|
|
359
|
-
getLastCheck: () => ({
|
|
360
|
-
lastCheckedAt: settings?.lastUpdateCheckAt ?? null,
|
|
361
|
-
dismissedVersion: settings?.dismissedUpdateVersion ?? null,
|
|
362
|
-
}),
|
|
363
|
-
onChecked: (lastCheckedAt) =>
|
|
364
|
-
updateSettings({ lastUpdateCheckAt: lastCheckedAt }),
|
|
365
|
-
});
|
|
366
|
-
|
|
367
|
-
if (update.status === "available") {
|
|
368
|
-
// update.release.version / .notes / .apkUrl
|
|
369
|
-
}
|
|
370
|
-
```
|
|
371
|
-
|
|
372
|
-
### `<UpdateAvailableBanner version notes onPress onDismiss />`
|
|
373
|
-
|
|
374
|
-
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.
|
|
375
|
-
|
|
376
|
-
```tsx
|
|
377
|
-
import { UpdateAvailableBanner } from "@forthtilliath/react-native-kit/components/update/UpdateAvailableBanner";
|
|
378
|
-
|
|
379
|
-
{
|
|
380
|
-
update.status === "available" && (
|
|
381
|
-
<UpdateAvailableBanner
|
|
382
|
-
version={update.release.version}
|
|
383
|
-
notes={update.release.notes}
|
|
384
|
-
onPress={() => router.push("/settings/update")}
|
|
385
|
-
onDismiss={() => {
|
|
386
|
-
dismissUpdateVersion(update.release.version);
|
|
387
|
-
update.dismiss();
|
|
388
|
-
}}
|
|
389
|
-
/>
|
|
390
|
-
);
|
|
391
|
-
}
|
|
392
|
-
```
|
|
303
|
+
> `useUpdateCheck`, `<UpdateAvailableBanner>` and `<ChangelogNotes>` moved to
|
|
304
|
+
> [`@forthtilliath/expo-release-updates-ui`](../expo-release-updates-ui#readme) —
|
|
305
|
+
> install that package alongside this one if you need them.
|
|
393
306
|
|
|
394
307
|
### Settings screens (`components/settings/`)
|
|
395
308
|
|
|
@@ -528,7 +441,7 @@ import { ThemeSettingsScreen } from "@forthtilliath/react-native-kit/components/
|
|
|
528
441
|
|
|
529
442
|
#### `<UpdateSettingsScreen currentVersion checkForUpdate compareVersions downloadAndInstallApk />`
|
|
530
443
|
|
|
531
|
-
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.
|
|
444
|
+
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. Renders release notes via `@forthtilliath/expo-release-updates-ui`'s `ChangelogNotes`, and pairs naturally with that package's `useUpdateCheck`/`UpdateAvailableBanner` (same `checkForUpdate`/`compareVersions` you already pass those), but takes plain functions so it isn't tied to that package's exact shape.
|
|
532
445
|
|
|
533
446
|
```tsx
|
|
534
447
|
import { UpdateSettingsScreen } from "@forthtilliath/react-native-kit/components/settings/UpdateSettingsScreen";
|
|
@@ -657,31 +570,20 @@ import { PrivacySettingsScreen } from "@forthtilliath/react-native-kit/component
|
|
|
657
570
|
|
|
658
571
|
For all 7 components above, styling works the same way as `ChangelogNotes`: an optional `styles` prop (all fields optional, neutral defaults), and where relevant an optional `labels` prop for the built-in French copy.
|
|
659
572
|
|
|
660
|
-
### Utils (`utils/`)
|
|
573
|
+
### Utils (`utils/helpers/`)
|
|
574
|
+
|
|
575
|
+
`confirmDestructive` is the only remaining util here — it wraps React Native's `Alert`, so it isn't framework-agnostic (see its own section above).
|
|
661
576
|
|
|
662
|
-
|
|
577
|
+
The framework-agnostic helpers that used to live here (`getPeriodStartMs`, `getMostRecentIds`, `nextInCycle`, `normalizeForSearch`, `rankByNameMatch`, `escapeCsvField`, `formatCsvNumber`, `escapeHtml`) moved to [`@forthtilliath/ts-kit`](../ts-kit#readme) — no React Native dependency on them, so they're usable from Node/web too. They're still re-exported from this package's root barrel for convenience:
|
|
663
578
|
|
|
664
579
|
```ts
|
|
665
|
-
import {
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
import { escapeHtml } from "@forthtilliath/react-native-kit/utils/format/escapeHtml";
|
|
673
|
-
```
|
|
674
|
-
|
|
675
|
-
| Function | What it does |
|
|
676
|
-
| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
677
|
-
| `getPeriodStartMs(period, now?)` | Start timestamp (ms) for a `"today" \| "7d" \| "30d" \| "all"` period filter, or `null` for `"all"`. `"today"` is the current calendar day, not a rolling 24h. |
|
|
678
|
-
| `getMostRecentIds(rows, limit?)` | Most recently occurring distinct ids (`{ id, occurredAt }[]`), most recent first, deduplicated, limited (default 5). |
|
|
679
|
-
| `nextInCycle(ids, currentId)` | Next id in a short list (e.g. cycling through recents on tap) — wraps to the first if `currentId` is at the end or no longer in the list. |
|
|
680
|
-
| `normalizeForSearch(text)` | Lowercases, trims, strips accents, and expands `œ`/`æ` ligatures (which `normalize("NFD")` alone doesn't decompose) — for accent/case-insensitive search matching. |
|
|
681
|
-
| `rankByNameMatch(items, query, getName)` | Ranks `items` by relevance to `query`: earlier match position first, then shorter name — for a search-as-you-type list. |
|
|
682
|
-
| `escapeCsvField(value)` | Quotes and escapes a CSV field (RFC 4180) only if it contains a `"`, `;`, or newline. |
|
|
683
|
-
| `formatCsvNumber(value, decimals?)` | Formats a number with a comma decimal separator (French-locale spreadsheets) instead of JS's dot. |
|
|
684
|
-
| `escapeHtml(text)` | Basic HTML entity escaping (`&`, `<`, `>`, `"`) for inserting user text into an HTML template. |
|
|
580
|
+
import {
|
|
581
|
+
getPeriodStartMs,
|
|
582
|
+
getMostRecentIds,
|
|
583
|
+
} from "@forthtilliath/react-native-kit";
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
Prefer importing straight from `ts-kit` (deep import, see its README) if you don't otherwise need this package.
|
|
685
587
|
|
|
686
588
|
## Scripts
|
|
687
589
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/components/index.ts"],"names":[],"mappings":"AAAA,cAAc,QAAQ,CAAC;AACvB,cAAc,UAAU,CAAC;AACzB,cAAc,YAAY,CAAC;AAC3B,cAAc,SAAS,CAAC"}
|
package/dist/components/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type {
|
|
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":"
|
|
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 {
|
|
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
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ImageStyle, StyleProp, TextStyle, ViewStyle } from "react-native";
|
|
2
|
+
import type { PickerItem } from "./PickerModal.js";
|
|
3
|
+
export interface PickerModalRowStyles {
|
|
4
|
+
row: StyleProp<ViewStyle>;
|
|
5
|
+
rowThumbnail: StyleProp<ImageStyle>;
|
|
6
|
+
rowThumbnailPlaceholder: StyleProp<ViewStyle>;
|
|
7
|
+
rowThumbnailPlaceholderIconColor: string;
|
|
8
|
+
rowLabel: StyleProp<TextStyle>;
|
|
9
|
+
rowSubtitle: StyleProp<TextStyle>;
|
|
10
|
+
}
|
|
11
|
+
export interface PickerModalRowProps {
|
|
12
|
+
item: PickerItem;
|
|
13
|
+
styles: PickerModalRowStyles;
|
|
14
|
+
onSelect: (item: PickerItem) => void;
|
|
15
|
+
}
|
|
16
|
+
export declare function PickerModalRow({ item, styles, onSelect, }: PickerModalRowProps): import("react").JSX.Element;
|
|
17
|
+
//# sourceMappingURL=PickerModalRow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PickerModalRow.d.ts","sourceRoot":"","sources":["../../../src/components/picker/PickerModalRow.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAIhF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC1B,YAAY,EAAE,SAAS,CAAC,UAAU,CAAC,CAAC;IACpC,uBAAuB,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC9C,gCAAgC,EAAE,MAAM,CAAC;IACzC,QAAQ,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;IAC/B,WAAW,EAAE,SAAS,CAAC,SAAS,CAAC,CAAC;CACnC;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,oBAAoB,CAAC;IAC7B,QAAQ,EAAE,CAAC,IAAI,EAAE,UAAU,KAAK,IAAI,CAAC;CACtC;AAKD,wBAAgB,cAAc,CAAC,EAC7B,IAAI,EACJ,MAAM,EACN,QAAQ,GACT,EAAE,mBAAmB,+BAmCrB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Image, Pressable, Text, View } from "react-native";
|
|
3
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
4
|
+
// One result row: thumbnail (photo, placeholder icon, or nothing depending
|
|
5
|
+
// on `imageUri`), label, and optional subtitle. Extracted from PickerModal's
|
|
6
|
+
// `renderItem` to keep that file under the project's 300-line-per-file rule.
|
|
7
|
+
export function PickerModalRow({ item, styles, onSelect, }) {
|
|
8
|
+
return (_jsxs(Pressable, { style: styles.row, onPress: () => {
|
|
9
|
+
onSelect(item);
|
|
10
|
+
}, accessibilityRole: "button", accessibilityLabel: item.subtitle ? `${item.label}, ${item.subtitle}` : item.label, children: [item.imageUri ? (_jsx(Image, { source: { uri: item.imageUri }, style: styles.rowThumbnail, accessibilityIgnoresInvertColors: true })) : item.imageUri === null ? (_jsx(View, { style: styles.rowThumbnailPlaceholder, children: _jsx(Ionicons, { name: "cube-outline", size: 18, color: styles.rowThumbnailPlaceholderIconColor }) })) : null, _jsxs(View, { style: { flex: 1 }, children: [_jsx(Text, { style: styles.rowLabel, children: item.label }), item.subtitle ? (_jsx(Text, { style: styles.rowSubtitle, children: item.subtitle })) : null] })] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { PickerItem } from "./PickerModal.js";
|
|
2
|
+
export interface PickerModalSection {
|
|
3
|
+
title: string | null;
|
|
4
|
+
data: PickerItem[];
|
|
5
|
+
}
|
|
6
|
+
export declare function usePickerModalSections(filtered: PickerItem[], isSearching: boolean, groupOrder: string[] | undefined, otherGroupLabel: string): PickerModalSection[];
|
|
7
|
+
//# sourceMappingURL=usePickerModalSections.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePickerModalSections.d.ts","sourceRoot":"","sources":["../../../src/components/picker/usePickerModalSections.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,IAAI,EAAE,UAAU,EAAE,CAAC;CACpB;AAMD,wBAAgB,sBAAsB,CACpC,QAAQ,EAAE,UAAU,EAAE,EACtB,WAAW,EAAE,OAAO,EACpB,UAAU,EAAE,MAAM,EAAE,GAAG,SAAS,EAChC,eAAe,EAAE,MAAM,GACtB,kBAAkB,EAAE,CAoBtB"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { useMemo } from "react";
|
|
2
|
+
// Sections sorted by title: stable order, independent of the source groups'
|
|
3
|
+
// order. No sections during a search (results ranked by relevance, not by
|
|
4
|
+
// group), nor if no item has a group (uncategorized pickers, unchanged
|
|
5
|
+
// behavior).
|
|
6
|
+
export function usePickerModalSections(filtered, isSearching, groupOrder, otherGroupLabel) {
|
|
7
|
+
return useMemo(() => {
|
|
8
|
+
if (isSearching || !filtered.some((item) => item.group)) {
|
|
9
|
+
return [{ title: null, data: filtered }];
|
|
10
|
+
}
|
|
11
|
+
const byGroup = new Map();
|
|
12
|
+
for (const item of filtered) {
|
|
13
|
+
const key = item.group ?? otherGroupLabel;
|
|
14
|
+
const group = byGroup.get(key);
|
|
15
|
+
if (group)
|
|
16
|
+
group.push(item);
|
|
17
|
+
else
|
|
18
|
+
byGroup.set(key, [item]);
|
|
19
|
+
}
|
|
20
|
+
function sortIndex(groupTitle) {
|
|
21
|
+
const index = groupOrder?.indexOf(groupTitle) ?? -1;
|
|
22
|
+
return index === -1 ? (groupOrder?.length ?? 0) : index;
|
|
23
|
+
}
|
|
24
|
+
return [...byGroup.entries()]
|
|
25
|
+
.sort(([a], [b]) => sortIndex(a) - sortIndex(b) || a.localeCompare(b))
|
|
26
|
+
.map(([sectionTitle, data]) => ({ title: sectionTitle, data }));
|
|
27
|
+
}, [filtered, isSearching, groupOrder, otherGroupLabel]);
|
|
28
|
+
}
|
|
@@ -1,49 +1,5 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
3
|
-
export interface UpdateSettingsScreenRelease {
|
|
4
|
-
version: string;
|
|
5
|
-
notes: string;
|
|
6
|
-
apkUrl: string;
|
|
7
|
-
}
|
|
8
|
-
export interface UpdateSettingsScreenHistoryEntry {
|
|
9
|
-
version: string;
|
|
10
|
-
notes: string;
|
|
11
|
-
publishedAt?: string | null;
|
|
12
|
-
}
|
|
13
|
-
export interface UpdateSettingsScreenLabels {
|
|
14
|
-
installedVersionLabel?: string;
|
|
15
|
-
checkButton?: string;
|
|
16
|
-
upToDate?: string;
|
|
17
|
-
checkError?: string;
|
|
18
|
-
downloadError?: string;
|
|
19
|
-
availableTitle?: (version: string) => string;
|
|
20
|
-
installButton?: (version: string) => string;
|
|
21
|
-
installButtonAccessibilityLabel?: (version: string) => string;
|
|
22
|
-
downloadingLabel?: (percent: number) => string;
|
|
23
|
-
downloadingHint?: string;
|
|
24
|
-
historyTitle?: string;
|
|
25
|
-
}
|
|
26
|
-
export interface UpdateSettingsScreenStyles {
|
|
27
|
-
container?: StyleProp<ViewStyle>;
|
|
28
|
-
infoBox?: StyleProp<ViewStyle>;
|
|
29
|
-
infoLabel?: StyleProp<TextStyle>;
|
|
30
|
-
infoValue?: StyleProp<TextStyle>;
|
|
31
|
-
helpText?: StyleProp<TextStyle>;
|
|
32
|
-
errorText?: StyleProp<TextStyle>;
|
|
33
|
-
button?: StyleProp<ViewStyle>;
|
|
34
|
-
buttonDisabled?: StyleProp<ViewStyle>;
|
|
35
|
-
buttonText?: StyleProp<TextStyle>;
|
|
36
|
-
activityIndicatorColor?: string;
|
|
37
|
-
updateAvailableBox?: StyleProp<ViewStyle>;
|
|
38
|
-
updateAvailableTitle?: StyleProp<TextStyle>;
|
|
39
|
-
changelog?: StyleProp<ViewStyle>;
|
|
40
|
-
changelogTitle?: StyleProp<TextStyle>;
|
|
41
|
-
changelogEntry?: StyleProp<ViewStyle>;
|
|
42
|
-
changelogEntryHeader?: StyleProp<ViewStyle>;
|
|
43
|
-
changelogVersion?: StyleProp<TextStyle>;
|
|
44
|
-
changelogDate?: StyleProp<TextStyle>;
|
|
45
|
-
notes?: ChangelogNotesStyles;
|
|
46
|
-
}
|
|
1
|
+
import type { UpdateSettingsScreenHistoryEntry, UpdateSettingsScreenLabels, UpdateSettingsScreenRelease, UpdateSettingsScreenStyles } from "./UpdateSettingsScreen.styles.js";
|
|
2
|
+
export type { UpdateSettingsScreenHistoryEntry, UpdateSettingsScreenLabels, UpdateSettingsScreenRelease, UpdateSettingsScreenStyles, } from "./UpdateSettingsScreen.styles.js";
|
|
47
3
|
export interface UpdateSettingsScreenProps {
|
|
48
4
|
currentVersion: string;
|
|
49
5
|
/** Fetches the latest available release, or `null` if there is none. */
|