@vvedantb/native-ui 0.7.0 → 0.8.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 +5 -1
- package/package.json +1 -1
- package/src/entries/layout.ts +18 -0
- package/src/examples/ParityGallery.tsx +11 -0
- package/src/index.ts +18 -0
- package/src/settings/MoreFromProjectV.tsx +105 -0
- package/src/settings/projectVMobileApps.ts +145 -0
package/README.md
CHANGED
|
@@ -68,7 +68,7 @@ The root entry exports everything. It still imports `BottomSheetContainer` and `
|
|
|
68
68
|
| `@vvedantb/native-ui/toast` | `toast`, `Toaster`, `ToastOptions`, `ToastVariant` | none (from 0.3.12) |
|
|
69
69
|
| `@vvedantb/native-ui/theme` | `NativeUiProvider`, `useNativeUi`, `useLargerText`, `useAccentColor`, `DEFAULT_ACCENT`, `tokens`, `palette`, `withAlpha`, `fontClass`, `cn`, `Colors` and the colour constants | none |
|
|
70
70
|
| `@vvedantb/native-ui/floating` | `FloatingSurface`, `GlassSheetBackground`, `LiquidGlass`, `GlassBlurProvider`, `GlassBlurTarget`, `FloatingFAB`, `useReduceTransparency`, the floating metrics and styles | `expo-blur` (loader) |
|
|
71
|
-
| `@vvedantb/native-ui/layout` | `ScreenContainer`, `SafeAreaContainer`, `PageTitle`, `PageHeader`, `StackPageHeader` (0.5.0) and their props types, `SectionLabel` (0.5.0), `SettingsSection`, `SettingsField`, `SettingsToggleRow`, `SettingsStack` | `expo-blur` (loader) |
|
|
71
|
+
| `@vvedantb/native-ui/layout` | `ScreenContainer`, `SafeAreaContainer`, `PageTitle`, `PageHeader`, `StackPageHeader` (0.5.0) and their props types, `SectionLabel` (0.5.0), `SettingsSection`, `SettingsField`, `SettingsToggleRow`, `SettingsStack`, `MoreFromProjectV` and `FromProjectV` (0.8.0) | `expo-blur` (loader) |
|
|
72
72
|
| `@vvedantb/native-ui/marketing` | `UpgradeToProCard`, `PricingModal` and the pricing types | none |
|
|
73
73
|
| `@vvedantb/native-ui/ambient` | `AmbientBackground` | `expo-linear-gradient` (loader) |
|
|
74
74
|
| `@vvedantb/native-ui/blur`, `/haptics` | the loaders | `expo-blur`, `expo-haptics` (loaders) |
|
|
@@ -302,6 +302,10 @@ From 0.3.12 a toast looks like the web `SonnerToaster` at its default density wi
|
|
|
302
302
|
|
|
303
303
|
`@vvedantb/native-ui/gallery` exports `ParityGallery`, a screen that renders these components. Put it in a dev-only route to check them on a device.
|
|
304
304
|
|
|
305
|
+
## More from Project V (0.8.0)
|
|
306
|
+
|
|
307
|
+
`<MoreFromProjectV current="velth" />` is a settings section listing the other Project V phone apps (vibot, Verve, Velth, vmem, V Store). A row opens the app if installed, else V Store's home (`vstore://`), else its website. Add the other apps' schemes to `LSApplicationQueriesSchemes` (iOS) and `<queries>` (Android 11+), or `canOpenURL` returns `false`. `<FromProjectV />` (or `variant="footer"`) is a small "from Project V" line. `openProjectVApp(id, Linking, Platform.OS)` and `PROJECT_V_MOBILE_APPS` are exported for custom rows.
|
|
308
|
+
|
|
305
309
|
## App pieces (0.5.0)
|
|
306
310
|
|
|
307
311
|
Components and props that the Verve, vibot and Velth apps need. They follow the rest of the package: Instrument Sans with its weight as a style, `tokens` and `palette` colours as styles in light and dark, a step up with larger text, 44 pt targets, and roles, names and states for VoiceOver and TalkBack.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vvedantb/native-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "Shared React Native / Expo UI primitives. Floating surfaces, forms, overlays, toasts, settings chrome, tab bar, AI chat parts, the family app marks, a NativeWind preset, the Expo web root and Clerk + Convex auth screens.",
|
|
6
6
|
"type": "module",
|
package/src/entries/layout.ts
CHANGED
|
@@ -12,3 +12,21 @@ export { SettingsSection } from "../settings/SettingsSection";
|
|
|
12
12
|
export { SettingsField } from "../settings/SettingsField";
|
|
13
13
|
export { SettingsToggleRow } from "../settings/SettingsToggleRow";
|
|
14
14
|
export { SettingsStack } from "../settings/SettingsStack";
|
|
15
|
+
export { MoreFromProjectV, FromProjectV } from "../settings/MoreFromProjectV";
|
|
16
|
+
export type { MoreFromProjectVProps, FromProjectVProps } from "../settings/MoreFromProjectV";
|
|
17
|
+
export {
|
|
18
|
+
PROJECT_V_MOBILE_APPS,
|
|
19
|
+
projectVMobileApp,
|
|
20
|
+
otherProjectVMobileApps,
|
|
21
|
+
openProjectVApp,
|
|
22
|
+
projectVAppOpenSteps,
|
|
23
|
+
VSTORE_HOME_URL,
|
|
24
|
+
androidIntentUrl,
|
|
25
|
+
} from "../settings/projectVMobileApps";
|
|
26
|
+
export type {
|
|
27
|
+
ProjectVMobileApp,
|
|
28
|
+
ProjectVMobileAppId,
|
|
29
|
+
ProjectVOpenTarget,
|
|
30
|
+
ProjectVOpenStep,
|
|
31
|
+
ProjectVLinking,
|
|
32
|
+
} from "../settings/projectVMobileApps";
|
|
@@ -54,12 +54,14 @@ import {
|
|
|
54
54
|
DialogTitle,
|
|
55
55
|
DialogTrigger,
|
|
56
56
|
EmptyState,
|
|
57
|
+
FromProjectV,
|
|
57
58
|
GoogleMark,
|
|
58
59
|
IconButton,
|
|
59
60
|
Input,
|
|
60
61
|
Label,
|
|
61
62
|
LabeledSwitchRow,
|
|
62
63
|
ListRow,
|
|
64
|
+
MoreFromProjectV,
|
|
63
65
|
NEUTRAL_900,
|
|
64
66
|
NativeUiProvider,
|
|
65
67
|
PageHeader,
|
|
@@ -682,6 +684,15 @@ function GalleryScreen() {
|
|
|
682
684
|
</AmbientBackground>
|
|
683
685
|
</Section>
|
|
684
686
|
|
|
687
|
+
<Section title="More from Project V">
|
|
688
|
+
{/* As in Velth's settings: the other four phone apps. A row opens the app, else V Store, else the website. */}
|
|
689
|
+
<MoreFromProjectV
|
|
690
|
+
current="velth"
|
|
691
|
+
onOpen={(id, target) => toast(target ? `Opened ${id} (${target})` : `Could not open ${id}`)}
|
|
692
|
+
/>
|
|
693
|
+
<FromProjectV />
|
|
694
|
+
</Section>
|
|
695
|
+
|
|
685
696
|
<Section title="Camera chrome">
|
|
686
697
|
{/* A dark stand-in for the live preview. */}
|
|
687
698
|
<View className="h-72 justify-between overflow-hidden rounded-lg p-4" style={{ backgroundColor: NEUTRAL_900 }}>
|
package/src/index.ts
CHANGED
|
@@ -164,6 +164,24 @@ export { SettingsSection } from "./settings/SettingsSection";
|
|
|
164
164
|
export { SettingsField } from "./settings/SettingsField";
|
|
165
165
|
export { SettingsToggleRow } from "./settings/SettingsToggleRow";
|
|
166
166
|
export { SettingsStack } from "./settings/SettingsStack";
|
|
167
|
+
export { MoreFromProjectV, FromProjectV } from "./settings/MoreFromProjectV";
|
|
168
|
+
export type { MoreFromProjectVProps, FromProjectVProps } from "./settings/MoreFromProjectV";
|
|
169
|
+
export {
|
|
170
|
+
PROJECT_V_MOBILE_APPS,
|
|
171
|
+
projectVMobileApp,
|
|
172
|
+
otherProjectVMobileApps,
|
|
173
|
+
openProjectVApp,
|
|
174
|
+
projectVAppOpenSteps,
|
|
175
|
+
VSTORE_HOME_URL,
|
|
176
|
+
androidIntentUrl,
|
|
177
|
+
} from "./settings/projectVMobileApps";
|
|
178
|
+
export type {
|
|
179
|
+
ProjectVMobileApp,
|
|
180
|
+
ProjectVMobileAppId,
|
|
181
|
+
ProjectVOpenTarget,
|
|
182
|
+
ProjectVOpenStep,
|
|
183
|
+
ProjectVLinking,
|
|
184
|
+
} from "./settings/projectVMobileApps";
|
|
167
185
|
export { PINK_50, PINK_100, PINK_600, PINK_700, NEUTRAL_100, NEUTRAL_500, NEUTRAL_900, Colors } from "./colors";
|
|
168
186
|
// Family app marks and their pure data (copied from `@vvedantb/ui/brand`), and `GoogleMark`.
|
|
169
187
|
export * from "./brand";
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { Linking, Platform, Text, View } from "react-native";
|
|
2
|
+
import { APP_ICONS } from "../brand/AppIcons";
|
|
3
|
+
import { AppMark } from "../brand/AppMark";
|
|
4
|
+
import type { AppName } from "../brand/svg";
|
|
5
|
+
import { cn } from "../cn";
|
|
6
|
+
import { textStyle, typeStyle } from "../controlStyles";
|
|
7
|
+
import { ListRow } from "../ListRow";
|
|
8
|
+
import { tokens } from "../tokens";
|
|
9
|
+
import { useScheme } from "../useScheme";
|
|
10
|
+
import { SettingsSection } from "./SettingsSection";
|
|
11
|
+
import {
|
|
12
|
+
openProjectVApp,
|
|
13
|
+
otherProjectVMobileApps,
|
|
14
|
+
type ProjectVMobileAppId,
|
|
15
|
+
type ProjectVOpenTarget,
|
|
16
|
+
} from "./projectVMobileApps";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The mark for each phone app. V Store has no mark of its own (it was left out
|
|
20
|
+
* of the brand marks in 0.3.9), so it shows the Project V mark.
|
|
21
|
+
*/
|
|
22
|
+
const MARK: Record<ProjectVMobileAppId, AppName> = {
|
|
23
|
+
vibot: "vibot",
|
|
24
|
+
verve: "verve",
|
|
25
|
+
velth: "velth",
|
|
26
|
+
vmem: "vmem",
|
|
27
|
+
vstore: "projectv",
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type FromProjectVProps = {
|
|
31
|
+
className?: string;
|
|
32
|
+
testID?: string;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
/** A small muted "from Project V" line with the Project V mark, for the end of a settings screen. */
|
|
36
|
+
export function FromProjectV({ className, testID }: FromProjectVProps) {
|
|
37
|
+
const scheme = useScheme();
|
|
38
|
+
const muted = tokens.color.muted[scheme];
|
|
39
|
+
const ProjectVMark = APP_ICONS.projectv;
|
|
40
|
+
return (
|
|
41
|
+
<View
|
|
42
|
+
testID={testID}
|
|
43
|
+
accessible
|
|
44
|
+
accessibilityLabel="from Project V"
|
|
45
|
+
className={cn("flex-row items-center justify-center gap-1.5 py-2", className)}
|
|
46
|
+
>
|
|
47
|
+
<Text className="text-sm font-instrument-sans" style={textStyle("muted", scheme)}>
|
|
48
|
+
from
|
|
49
|
+
</Text>
|
|
50
|
+
<ProjectVMark size={14} color={muted} />
|
|
51
|
+
<Text
|
|
52
|
+
className="text-sm font-instrument-sans-semibold"
|
|
53
|
+
style={[textStyle("muted", scheme), typeStyle("semibold")]}
|
|
54
|
+
>
|
|
55
|
+
Project V
|
|
56
|
+
</Text>
|
|
57
|
+
</View>
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export type MoreFromProjectVProps = {
|
|
62
|
+
/** The app showing the section. It is left out of the list. */
|
|
63
|
+
current: ProjectVMobileAppId;
|
|
64
|
+
/** `"section"` (default): a settings section listing the other apps. `"footer"`: only the small "from Project V" line. */
|
|
65
|
+
variant?: "section" | "footer";
|
|
66
|
+
/** Section title. Default "More from Project V". */
|
|
67
|
+
title?: string;
|
|
68
|
+
/** Called after a row opens something: the app, V Store or its website (`null` if nothing opened). */
|
|
69
|
+
onOpen?: (id: ProjectVMobileAppId, target: ProjectVOpenTarget | null) => void;
|
|
70
|
+
className?: string;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Settings section listing the other Project V phone apps, each with its mark,
|
|
75
|
+
* name and one line. A row opens the app if it is installed, else V Store's
|
|
76
|
+
* home, else its website (`openProjectVApp`).
|
|
77
|
+
*/
|
|
78
|
+
export function MoreFromProjectV({
|
|
79
|
+
current,
|
|
80
|
+
variant = "section",
|
|
81
|
+
title = "More from Project V",
|
|
82
|
+
onOpen,
|
|
83
|
+
className,
|
|
84
|
+
}: MoreFromProjectVProps) {
|
|
85
|
+
if (variant === "footer") return <FromProjectV className={className} />;
|
|
86
|
+
return (
|
|
87
|
+
<SettingsSection title={title} bodyVariant="list" className={className}>
|
|
88
|
+
<View className="flex-col gap-1 p-1">
|
|
89
|
+
{otherProjectVMobileApps(current).map((app) => (
|
|
90
|
+
<ListRow
|
|
91
|
+
key={app.id}
|
|
92
|
+
testID={`more-from-project-v-${app.id}`}
|
|
93
|
+
title={app.name}
|
|
94
|
+
meta={app.description}
|
|
95
|
+
leading={<AppMark app={MARK[app.id]} size={28} />}
|
|
96
|
+
accessibilityLabel={`${app.name}, ${app.description}`}
|
|
97
|
+
onPress={() => {
|
|
98
|
+
void openProjectVApp(app.id, Linking, Platform.OS).then((target) => onOpen?.(app.id, target));
|
|
99
|
+
}}
|
|
100
|
+
/>
|
|
101
|
+
))}
|
|
102
|
+
</View>
|
|
103
|
+
</SettingsSection>
|
|
104
|
+
);
|
|
105
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { familyApp } from "../brand/familyApps";
|
|
2
|
+
|
|
3
|
+
/** The Project V phone apps, in display order. */
|
|
4
|
+
export type ProjectVMobileAppId = "vibot" | "verve" | "velth" | "vmem" | "vstore";
|
|
5
|
+
|
|
6
|
+
export type ProjectVMobileApp = {
|
|
7
|
+
id: ProjectVMobileAppId;
|
|
8
|
+
/** Display name. */
|
|
9
|
+
name: string;
|
|
10
|
+
/** One line for a settings row. */
|
|
11
|
+
description: string;
|
|
12
|
+
/** Android application id. */
|
|
13
|
+
androidPackage: string;
|
|
14
|
+
/** The app's URL scheme, without `://`. */
|
|
15
|
+
scheme: string;
|
|
16
|
+
/** Home page of the app: the fallback when neither the app nor V Store is installed. */
|
|
17
|
+
website: string;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* V Store's home screen. V Store's `app/[id]` route takes the app's Convex
|
|
22
|
+
* document `_id`, not its catalog id or Android package, and native-ui cannot
|
|
23
|
+
* know live Convex ids. So the store step opens V Store's home, not the app's page.
|
|
24
|
+
*/
|
|
25
|
+
export const VSTORE_HOME_URL = "vstore://";
|
|
26
|
+
|
|
27
|
+
// Names and sites come from `FAMILY_APPS` where it has the app. V Store is not
|
|
28
|
+
// a family app there (that file is a byte-identical copy of `@vvedantb/ui`).
|
|
29
|
+
const vibot = familyApp("vibot");
|
|
30
|
+
const verve = familyApp("verve");
|
|
31
|
+
const velth = familyApp("velth");
|
|
32
|
+
const vmem = familyApp("vmem");
|
|
33
|
+
|
|
34
|
+
/** The five Project V phone apps, in display order. */
|
|
35
|
+
export const PROJECT_V_MOBILE_APPS: readonly ProjectVMobileApp[] = [
|
|
36
|
+
{
|
|
37
|
+
id: "vibot",
|
|
38
|
+
name: vibot.name,
|
|
39
|
+
description: "AI chat, agents and tools",
|
|
40
|
+
androidPackage: "com.projectvdotuk.vibot",
|
|
41
|
+
scheme: "vibot",
|
|
42
|
+
website: vibot.url,
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "verve",
|
|
46
|
+
name: verve.name,
|
|
47
|
+
description: "Notes, pages and writing",
|
|
48
|
+
androidPackage: "com.projectvdotuk.verve",
|
|
49
|
+
scheme: "verve",
|
|
50
|
+
website: verve.url,
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
id: "velth",
|
|
54
|
+
name: velth.name,
|
|
55
|
+
description: "Health insights and camera scans",
|
|
56
|
+
androidPackage: "com.projectvdotuk.velth",
|
|
57
|
+
scheme: "velth",
|
|
58
|
+
website: velth.url,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "vmem",
|
|
62
|
+
name: vmem.name,
|
|
63
|
+
description: "Your personal memory",
|
|
64
|
+
androidPackage: "com.projectvdotuk.vmem",
|
|
65
|
+
scheme: "vmem",
|
|
66
|
+
website: vmem.url,
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "vstore",
|
|
70
|
+
name: "V Store",
|
|
71
|
+
description: "Install Project V apps",
|
|
72
|
+
androidPackage: "com.projectvdotuk.vstore",
|
|
73
|
+
scheme: "vstore",
|
|
74
|
+
website: "https://v-store-chi.vercel.app",
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
/** Entry of `PROJECT_V_MOBILE_APPS` for an app. */
|
|
79
|
+
export function projectVMobileApp(id: ProjectVMobileAppId): ProjectVMobileApp {
|
|
80
|
+
return PROJECT_V_MOBILE_APPS.find((app) => app.id === id)!;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** The phone apps other than `current`, in display order. */
|
|
84
|
+
export function otherProjectVMobileApps(current?: ProjectVMobileAppId): ProjectVMobileApp[] {
|
|
85
|
+
return PROJECT_V_MOBILE_APPS.filter((app) => app.id !== current);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Android intent URL that opens the installed app with this package. */
|
|
89
|
+
export function androidIntentUrl(app: ProjectVMobileApp): string {
|
|
90
|
+
return `intent://#Intent;scheme=${app.scheme};package=${app.androidPackage};end`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** How `openProjectVApp` opened an app. */
|
|
94
|
+
export type ProjectVOpenTarget = "app" | "store" | "website";
|
|
95
|
+
|
|
96
|
+
/** One URL to try, in order. `check`: ask `canOpenURL` first; otherwise try `openURL` and move on if it fails. */
|
|
97
|
+
export type ProjectVOpenStep = {
|
|
98
|
+
target: ProjectVOpenTarget;
|
|
99
|
+
url: string;
|
|
100
|
+
check: boolean;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* The URLs `openProjectVApp` tries, in order: the app's scheme; on Android an
|
|
105
|
+
* intent to its package; V Store's home (`VSTORE_HOME_URL`); the website.
|
|
106
|
+
*/
|
|
107
|
+
export function projectVAppOpenSteps(id: ProjectVMobileAppId, os: string): ProjectVOpenStep[] {
|
|
108
|
+
const app = projectVMobileApp(id);
|
|
109
|
+
const steps: ProjectVOpenStep[] = [{ target: "app", url: `${app.scheme}://`, check: true }];
|
|
110
|
+
if (os === "android") steps.push({ target: "app", url: androidIntentUrl(app), check: false });
|
|
111
|
+
if (id !== "vstore") steps.push({ target: "store", url: VSTORE_HOME_URL, check: true });
|
|
112
|
+
steps.push({ target: "website", url: app.website, check: false });
|
|
113
|
+
return steps;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** The part of React Native `Linking` that `openProjectVApp` uses. */
|
|
117
|
+
export type ProjectVLinking = {
|
|
118
|
+
canOpenURL(url: string): Promise<boolean>;
|
|
119
|
+
openURL(url: string): Promise<unknown>;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Opens a Project V app: the app if installed, else V Store's home if V Store
|
|
124
|
+
* is installed, else its website. Resolves to what it opened, or
|
|
125
|
+
* `null` when every URL failed. Pass React Native `Linking` and `Platform.OS`.
|
|
126
|
+
*
|
|
127
|
+
* `canOpenURL` needs the schemes declared by the calling app:
|
|
128
|
+
* `LSApplicationQueriesSchemes` on iOS and `<queries>` on Android 11+.
|
|
129
|
+
*/
|
|
130
|
+
export async function openProjectVApp(
|
|
131
|
+
id: ProjectVMobileAppId,
|
|
132
|
+
linking: ProjectVLinking,
|
|
133
|
+
os: string,
|
|
134
|
+
): Promise<ProjectVOpenTarget | null> {
|
|
135
|
+
for (const step of projectVAppOpenSteps(id, os)) {
|
|
136
|
+
try {
|
|
137
|
+
if (step.check && !(await linking.canOpenURL(step.url))) continue;
|
|
138
|
+
await linking.openURL(step.url);
|
|
139
|
+
return step.target;
|
|
140
|
+
} catch {
|
|
141
|
+
// Not installed, or no handler: try the next URL.
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return null;
|
|
145
|
+
}
|