@vinaverse/react-native 0.0.0-stage → 0.1.0-alpha.10
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/LICENSE +202 -0
- package/README.md +302 -2
- package/dist/ask.cjs +62 -0
- package/dist/ask.cjs.map +1 -0
- package/dist/ask.d.cts +38 -0
- package/dist/ask.d.ts +38 -0
- package/dist/ask.js +5 -0
- package/dist/ask.js.map +1 -0
- package/dist/chat.cjs +273 -0
- package/dist/chat.cjs.map +1 -0
- package/dist/chat.d.cts +490 -0
- package/dist/chat.d.ts +490 -0
- package/dist/chat.js +4 -0
- package/dist/chat.js.map +1 -0
- package/dist/chunk-43I4ZAEK.js +2607 -0
- package/dist/chunk-43I4ZAEK.js.map +1 -0
- package/dist/chunk-6QYDPGRS.cjs +97 -0
- package/dist/chunk-6QYDPGRS.cjs.map +1 -0
- package/dist/chunk-7KPCO443.js +74 -0
- package/dist/chunk-7KPCO443.js.map +1 -0
- package/dist/chunk-EFEXTVIT.js +88 -0
- package/dist/chunk-EFEXTVIT.js.map +1 -0
- package/dist/chunk-MG32E4JI.cjs +268 -0
- package/dist/chunk-MG32E4JI.cjs.map +1 -0
- package/dist/chunk-NMKJ7LIJ.cjs +76 -0
- package/dist/chunk-NMKJ7LIJ.cjs.map +1 -0
- package/dist/chunk-NRYNK7E4.cjs +2699 -0
- package/dist/chunk-NRYNK7E4.cjs.map +1 -0
- package/dist/chunk-POJP52GE.js +261 -0
- package/dist/chunk-POJP52GE.js.map +1 -0
- package/dist/index.cjs +303 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +11 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/launcher.cjs +86 -0
- package/dist/launcher.cjs.map +1 -0
- package/dist/launcher.d.cts +122 -0
- package/dist/launcher.d.ts +122 -0
- package/dist/launcher.js +5 -0
- package/dist/launcher.js.map +1 -0
- package/dist/theme-BhiEGv64.d.cts +35 -0
- package/dist/theme-BhiEGv64.d.ts +35 -0
- package/package.json +242 -3
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var tokens = require('@vinaverse/tokens');
|
|
4
|
+
var react = require('react');
|
|
5
|
+
var reactNative = require('react-native');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
// src/theme.tsx
|
|
9
|
+
function createTheme(scheme, overrides = {}) {
|
|
10
|
+
return {
|
|
11
|
+
scheme,
|
|
12
|
+
colors: { ...tokens.colors(scheme), ...overrides[scheme] },
|
|
13
|
+
font: {
|
|
14
|
+
...tokens.font,
|
|
15
|
+
...overrides.fontFamily ? { family: overrides.fontFamily } : {}
|
|
16
|
+
},
|
|
17
|
+
space: { ...tokens.space, ...overrides.space },
|
|
18
|
+
radius: { ...tokens.radius, ...overrides.radius },
|
|
19
|
+
motion: tokens.motion,
|
|
20
|
+
size: tokens.size
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function themeProblems(overrides = {}) {
|
|
24
|
+
return ["light", "dark"].flatMap(
|
|
25
|
+
(scheme) => tokens.contrastProblems(createTheme(scheme, overrides).colors).map(
|
|
26
|
+
(p) => `${scheme}: ${p.foreground} on ${p.background} is ${p.ratio.toFixed(2)}:1, below ${p.minimum}:1`
|
|
27
|
+
)
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
var ThemeContext = /* @__PURE__ */ react.createContext(
|
|
31
|
+
/* @__PURE__ */ createTheme("light")
|
|
32
|
+
);
|
|
33
|
+
function ThemeProvider(props) {
|
|
34
|
+
const system = reactNative.useColorScheme();
|
|
35
|
+
const setting = props.colorScheme ?? "system";
|
|
36
|
+
const scheme = setting === "system" ? system === "dark" ? "dark" : "light" : setting;
|
|
37
|
+
const overrides = props.theme;
|
|
38
|
+
const value = react.useMemo(
|
|
39
|
+
() => createTheme(scheme, overrides),
|
|
40
|
+
[scheme, overrides]
|
|
41
|
+
);
|
|
42
|
+
return /* @__PURE__ */ jsxRuntime.jsx(ThemeContext.Provider, { value, children: props.children });
|
|
43
|
+
}
|
|
44
|
+
function useTheme() {
|
|
45
|
+
return react.useContext(ThemeContext);
|
|
46
|
+
}
|
|
47
|
+
function useAgentSweep(runtime) {
|
|
48
|
+
react.useEffect(() => {
|
|
49
|
+
const subscription = reactNative.AppState.addEventListener("change", (state) => {
|
|
50
|
+
if (state === "active") runtime.sweep();
|
|
51
|
+
});
|
|
52
|
+
return () => subscription.remove();
|
|
53
|
+
}, [runtime]);
|
|
54
|
+
}
|
|
55
|
+
function useNativeLifecycle(store) {
|
|
56
|
+
react.useEffect(() => {
|
|
57
|
+
const apply = (state) => store.setForeground(state === "active");
|
|
58
|
+
apply(reactNative.AppState.currentState);
|
|
59
|
+
const subscription = reactNative.AppState.addEventListener("change", apply);
|
|
60
|
+
return () => subscription.remove();
|
|
61
|
+
}, [store]);
|
|
62
|
+
}
|
|
63
|
+
function useReducedMotion() {
|
|
64
|
+
const [reduced, setReduced] = react.useState(false);
|
|
65
|
+
react.useEffect(() => {
|
|
66
|
+
let live = true;
|
|
67
|
+
reactNative.AccessibilityInfo.isReduceMotionEnabled().then(
|
|
68
|
+
(value) => {
|
|
69
|
+
if (live) setReduced(value);
|
|
70
|
+
},
|
|
71
|
+
() => void 0
|
|
72
|
+
);
|
|
73
|
+
const subscription = reactNative.AccessibilityInfo.addEventListener(
|
|
74
|
+
"reduceMotionChanged",
|
|
75
|
+
setReduced
|
|
76
|
+
);
|
|
77
|
+
return () => {
|
|
78
|
+
live = false;
|
|
79
|
+
subscription.remove();
|
|
80
|
+
};
|
|
81
|
+
}, []);
|
|
82
|
+
return reduced;
|
|
83
|
+
}
|
|
84
|
+
function announce(text) {
|
|
85
|
+
if (text) reactNative.AccessibilityInfo.announceForAccessibility(text);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
exports.ThemeProvider = ThemeProvider;
|
|
89
|
+
exports.announce = announce;
|
|
90
|
+
exports.createTheme = createTheme;
|
|
91
|
+
exports.themeProblems = themeProblems;
|
|
92
|
+
exports.useAgentSweep = useAgentSweep;
|
|
93
|
+
exports.useNativeLifecycle = useNativeLifecycle;
|
|
94
|
+
exports.useReducedMotion = useReducedMotion;
|
|
95
|
+
exports.useTheme = useTheme;
|
|
96
|
+
//# sourceMappingURL=chunk-6QYDPGRS.cjs.map
|
|
97
|
+
//# sourceMappingURL=chunk-6QYDPGRS.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/theme.tsx","../src/platform.ts"],"names":["colors","font","space","radius","motion","size","contrastProblems","createContext","useColorScheme","useMemo","useContext","useEffect","AppState","useState","AccessibilityInfo"],"mappings":"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","file":"chunk-6QYDPGRS.cjs","sourcesContent":["// The theme: the shared design tokens, a tenant's brand on top, light and dark. A tenant changes colors, fonts, radii\n// and spacing; the disclosure bar keeps a legible pair whatever the tenant's colors (see DisclosureBar).\nimport {\n type ColorScheme,\n type Colors,\n colors,\n contrastProblems,\n font,\n motion,\n radius,\n size,\n space,\n} from '@vinaverse/tokens';\nimport { createContext, type ReactNode, useContext, useMemo } from 'react';\nimport { useColorScheme } from 'react-native';\n\nexport interface Theme {\n readonly scheme: ColorScheme;\n readonly colors: Colors;\n readonly font: typeof font & { readonly family?: string };\n readonly space: typeof space;\n readonly radius: typeof radius;\n readonly motion: typeof motion;\n readonly size: typeof size;\n}\n\n/** A tenant's brand: colors per scheme, a font family, radii. Anything left out keeps the Vinaverse default. */\nexport interface ThemeOverrides {\n readonly light?: Partial<Colors>;\n readonly dark?: Partial<Colors>;\n readonly fontFamily?: string;\n readonly radius?: Partial<typeof radius>;\n readonly space?: Partial<typeof space>;\n}\n\nexport type ColorSchemeSetting = 'system' | 'light' | 'dark';\n\n/** The theme for a scheme, with the tenant's overrides on top. */\nexport function createTheme(\n scheme: ColorScheme,\n overrides: ThemeOverrides = {},\n): Theme {\n return {\n scheme,\n colors: { ...colors(scheme), ...overrides[scheme] },\n font: {\n ...font,\n ...(overrides.fontFamily ? { family: overrides.fontFamily } : {}),\n },\n space: { ...space, ...overrides.space },\n radius: { ...radius, ...overrides.radius },\n motion,\n size,\n };\n}\n\n/** The pairs a tenant's colors made hard to read, in either scheme, as messages for a development warning. */\nexport function themeProblems(overrides: ThemeOverrides = {}): string[] {\n return (['light', 'dark'] as const).flatMap((scheme) =>\n contrastProblems(createTheme(scheme, overrides).colors).map(\n (p) =>\n `${scheme}: ${p.foreground} on ${p.background} is ${p.ratio.toFixed(2)}:1, below ${p.minimum}:1`,\n ),\n );\n}\n\nconst ThemeContext = /* @__PURE__ */ createContext<Theme>(\n /* @__PURE__ */ createTheme('light'),\n);\n\nexport function ThemeProvider(props: {\n readonly theme?: ThemeOverrides | undefined;\n readonly colorScheme?: ColorSchemeSetting | undefined;\n readonly children?: ReactNode;\n}): ReactNode {\n const system = useColorScheme();\n const setting = props.colorScheme ?? 'system';\n const scheme: ColorScheme =\n setting === 'system' ? (system === 'dark' ? 'dark' : 'light') : setting;\n const overrides = props.theme;\n const value = useMemo(\n () => createTheme(scheme, overrides),\n [scheme, overrides],\n );\n return (\n <ThemeContext.Provider value={value}>\n {props.children}\n </ThemeContext.Provider>\n );\n}\n\nexport function useTheme(): Theme {\n return useContext(ThemeContext);\n}\n","// The React Native platform adapter: what the conversation store learns from the app (foreground, background) and\n// what the kit asks of the platform (reduced motion, screen reader announcements).\nimport { useEffect, useState } from 'react';\nimport { AccessibilityInfo, AppState, type AppStateStatus } from 'react-native';\n\n/** The part of the conversation store the adapter drives. */\nexport interface LifecycleTarget {\n setForeground(foreground: boolean): void;\n}\n\n/**\n * When the app comes back to the foreground, the agent runtime ends the conversations no view shows that are past\n * their session's end (timers do not run in the background). Every agent part calls it; once is enough.\n */\nexport function useAgentSweep(runtime: { sweep(): void }): void {\n useEffect(() => {\n const subscription = AppState.addEventListener('change', (state) => {\n if (state === 'active') runtime.sweep();\n });\n return () => subscription.remove();\n }, [runtime]);\n}\n\n/** Tells the store when the app comes to the foreground or goes to the background (it pings only in the foreground). */\nexport function useNativeLifecycle(store: LifecycleTarget): void {\n useEffect(() => {\n const apply = (state: AppStateStatus) =>\n store.setForeground(state === 'active');\n apply(AppState.currentState);\n const subscription = AppState.addEventListener('change', apply);\n return () => subscription.remove();\n }, [store]);\n}\n\n/** Whether the person asked for reduced motion: animations and animated scrolling stay off. */\nexport function useReducedMotion(): boolean {\n const [reduced, setReduced] = useState(false);\n useEffect(() => {\n let live = true;\n AccessibilityInfo.isReduceMotionEnabled().then(\n (value) => {\n if (live) setReduced(value);\n },\n () => undefined,\n );\n const subscription = AccessibilityInfo.addEventListener(\n 'reduceMotionChanged',\n setReduced,\n );\n return () => {\n live = false;\n subscription.remove();\n };\n }, []);\n return reduced;\n}\n\n/** Reads a line to a screen reader, without moving focus. */\nexport function announce(text: string): void {\n if (text) AccessibilityInfo.announceForAccessibility(text);\n}\n"]}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { ThemeProvider, useTheme, useAgentSweep } from './chunk-EFEXTVIT.js';
|
|
2
|
+
import { CopyProvider, useCopy } from '@vinaverse/react';
|
|
3
|
+
import { useAgentName, useAgentRuntime, useAgentStatus } from '@vinaverse/react/agent';
|
|
4
|
+
import { useState } from 'react';
|
|
5
|
+
import { Pressable, View, Text } from 'react-native';
|
|
6
|
+
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
7
|
+
|
|
8
|
+
function AskAboutRow(props) {
|
|
9
|
+
return /* @__PURE__ */ jsx(CopyProvider, { locale: props.locale, copy: props.copy, children: /* @__PURE__ */ jsx(ThemeProvider, { theme: props.theme, colorScheme: props.colorScheme, children: /* @__PURE__ */ jsx(Row, { ...props }) }) });
|
|
10
|
+
}
|
|
11
|
+
function Row(props) {
|
|
12
|
+
const { t } = useCopy();
|
|
13
|
+
const { colors, radius, space, font } = useTheme();
|
|
14
|
+
const name = useAgentName(t("agentFallbackName"));
|
|
15
|
+
const runtime = useAgentRuntime();
|
|
16
|
+
useAgentStatus();
|
|
17
|
+
useAgentSweep(runtime);
|
|
18
|
+
const [shows] = useState(() => !!runtime.getStatus()?.available);
|
|
19
|
+
if (!shows) return null;
|
|
20
|
+
const { Mark, appearance: look } = props;
|
|
21
|
+
return /* @__PURE__ */ jsxs(
|
|
22
|
+
Pressable,
|
|
23
|
+
{
|
|
24
|
+
accessibilityRole: "button",
|
|
25
|
+
accessibilityLabel: props.body ? `${props.title}. ${props.body}` : props.title,
|
|
26
|
+
onPress: () => props.onPress({ entryPoint: "ask" }),
|
|
27
|
+
style: ({ pressed }) => ({
|
|
28
|
+
flexDirection: "row",
|
|
29
|
+
alignItems: "center",
|
|
30
|
+
gap: space.md,
|
|
31
|
+
padding: space.md,
|
|
32
|
+
borderRadius: radius.lg,
|
|
33
|
+
backgroundColor: look?.background ?? colors.surface,
|
|
34
|
+
opacity: pressed ? 0.85 : 1
|
|
35
|
+
}),
|
|
36
|
+
testID: props.testID ?? "vinaverse-ask-about",
|
|
37
|
+
children: [
|
|
38
|
+
/* @__PURE__ */ jsx(
|
|
39
|
+
View,
|
|
40
|
+
{
|
|
41
|
+
style: {
|
|
42
|
+
width: 36,
|
|
43
|
+
height: 36,
|
|
44
|
+
borderRadius: 18,
|
|
45
|
+
alignItems: "center",
|
|
46
|
+
justifyContent: "center",
|
|
47
|
+
backgroundColor: colors.primary
|
|
48
|
+
},
|
|
49
|
+
children: Mark ? /* @__PURE__ */ jsx(Mark, { size: 22, color: colors.onPrimary }) : /* @__PURE__ */ jsx(Text, { style: { color: colors.onPrimary, fontWeight: "600" }, children: (name.trim()[0] ?? "?").toUpperCase() })
|
|
50
|
+
}
|
|
51
|
+
),
|
|
52
|
+
/* @__PURE__ */ jsxs(View, { style: { flex: 1, gap: space.xxs }, children: [
|
|
53
|
+
/* @__PURE__ */ jsx(
|
|
54
|
+
Text,
|
|
55
|
+
{
|
|
56
|
+
style: {
|
|
57
|
+
color: colors.text,
|
|
58
|
+
fontSize: font.size.md,
|
|
59
|
+
fontWeight: "600"
|
|
60
|
+
},
|
|
61
|
+
children: props.title
|
|
62
|
+
}
|
|
63
|
+
),
|
|
64
|
+
props.body ? /* @__PURE__ */ jsx(Text, { style: { color: colors.textMuted, fontSize: font.size.sm }, children: props.body }) : null
|
|
65
|
+
] }),
|
|
66
|
+
look?.chevron === false ? null : /* @__PURE__ */ jsx(Text, { style: { color: colors.textMuted, fontSize: font.size.lg }, children: "\u203A" })
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export { AskAboutRow };
|
|
73
|
+
//# sourceMappingURL=chunk-7KPCO443.js.map
|
|
74
|
+
//# sourceMappingURL=chunk-7KPCO443.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/AskAboutRow.tsx"],"names":[],"mappings":"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","file":"chunk-7KPCO443.js","sourcesContent":["// \"Ask the agent about this\" on one of the app's own pages (a professional's, a space's, a booking's): a row that opens\n// the agent told the place. It shows only when the agent is available, decided once as the page draws, from the status\n// already read, so it never appears or disappears under content the person is reading.\nimport { type CopyOverrides, CopyProvider, useCopy } from '@vinaverse/react';\nimport {\n useAgentName,\n useAgentRuntime,\n useAgentStatus,\n} from '@vinaverse/react/agent';\nimport { type ComponentType, type ReactNode, useState } from 'react';\nimport { Pressable, Text, View } from 'react-native';\nimport { useAgentSweep } from '../platform.js';\nimport {\n type ColorSchemeSetting,\n type ThemeOverrides,\n ThemeProvider,\n useTheme,\n} from '../theme.js';\n\n/** The row's look beyond the theme. */\nexport interface AskAboutRowAppearance {\n /** A tint behind the row; default the theme's `surface`. */\n readonly background?: string;\n /** The chevron at the row's end. Default true. */\n readonly chevron?: boolean;\n}\n\nexport interface AskAboutRowProps {\n /** \"Ask Example Agent about Riley Sample\". */\n readonly title: string;\n readonly body?: string;\n /** The person asks: open the chat with `entryPoint` (`ask`), so the session says where it was opened from. */\n readonly onPress: (from: { readonly entryPoint: 'ask' }) => void;\n /** The app's brand mark at the row's start; default the agent name's first letter. */\n readonly Mark?: ComponentType<{\n readonly size: number;\n readonly color: string;\n }>;\n readonly appearance?: AskAboutRowAppearance;\n readonly locale?: string;\n readonly copy?: CopyOverrides;\n readonly theme?: ThemeOverrides;\n readonly colorScheme?: ColorSchemeSetting;\n /** The row's test id. Default `vinaverse-ask-about`. */\n readonly testID?: string;\n}\n\nexport function AskAboutRow(props: AskAboutRowProps): ReactNode {\n return (\n <CopyProvider locale={props.locale} copy={props.copy}>\n <ThemeProvider theme={props.theme} colorScheme={props.colorScheme}>\n <Row {...props} />\n </ThemeProvider>\n </CopyProvider>\n );\n}\n\nfunction Row(props: AskAboutRowProps): ReactNode {\n const { t } = useCopy();\n const { colors, radius, space, font } = useTheme();\n const name = useAgentName(t('agentFallbackName'));\n const runtime = useAgentRuntime();\n useAgentStatus(); // reads the status when none is held, for the pages after this one\n useAgentSweep(runtime);\n // Decided once, as the page draws, from the status already read: an agent unknown or unavailable then stays away for\n // the visit, and the row is never added under content the person is reading.\n const [shows] = useState(() => !!runtime.getStatus()?.available);\n if (!shows) return null;\n const { Mark, appearance: look } = props;\n return (\n <Pressable\n accessibilityRole=\"button\"\n accessibilityLabel={\n props.body ? `${props.title}. ${props.body}` : props.title\n }\n onPress={() => props.onPress({ entryPoint: 'ask' })}\n style={({ pressed }) => ({\n flexDirection: 'row',\n alignItems: 'center',\n gap: space.md,\n padding: space.md,\n borderRadius: radius.lg,\n backgroundColor: look?.background ?? colors.surface,\n opacity: pressed ? 0.85 : 1,\n })}\n testID={props.testID ?? 'vinaverse-ask-about'}\n >\n <View\n style={{\n width: 36,\n height: 36,\n borderRadius: 18,\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: colors.primary,\n }}\n >\n {Mark ? (\n <Mark size={22} color={colors.onPrimary} />\n ) : (\n <Text style={{ color: colors.onPrimary, fontWeight: '600' }}>\n {(name.trim()[0] ?? '?').toUpperCase()}\n </Text>\n )}\n </View>\n <View style={{ flex: 1, gap: space.xxs }}>\n <Text\n style={{\n color: colors.text,\n fontSize: font.size.md,\n fontWeight: '600',\n }}\n >\n {props.title}\n </Text>\n {props.body ? (\n <Text style={{ color: colors.textMuted, fontSize: font.size.sm }}>\n {props.body}\n </Text>\n ) : null}\n </View>\n {look?.chevron === false ? null : (\n <Text style={{ color: colors.textMuted, fontSize: font.size.lg }}>\n ›\n </Text>\n )}\n </Pressable>\n );\n}\n"]}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { size, motion, radius, space, font, colors, contrastProblems } from '@vinaverse/tokens';
|
|
2
|
+
import { useMemo, useContext, useEffect, useState, createContext } from 'react';
|
|
3
|
+
import { useColorScheme, AppState, AccessibilityInfo } from 'react-native';
|
|
4
|
+
import { jsx } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
// src/theme.tsx
|
|
7
|
+
function createTheme(scheme, overrides = {}) {
|
|
8
|
+
return {
|
|
9
|
+
scheme,
|
|
10
|
+
colors: { ...colors(scheme), ...overrides[scheme] },
|
|
11
|
+
font: {
|
|
12
|
+
...font,
|
|
13
|
+
...overrides.fontFamily ? { family: overrides.fontFamily } : {}
|
|
14
|
+
},
|
|
15
|
+
space: { ...space, ...overrides.space },
|
|
16
|
+
radius: { ...radius, ...overrides.radius },
|
|
17
|
+
motion,
|
|
18
|
+
size
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function themeProblems(overrides = {}) {
|
|
22
|
+
return ["light", "dark"].flatMap(
|
|
23
|
+
(scheme) => contrastProblems(createTheme(scheme, overrides).colors).map(
|
|
24
|
+
(p) => `${scheme}: ${p.foreground} on ${p.background} is ${p.ratio.toFixed(2)}:1, below ${p.minimum}:1`
|
|
25
|
+
)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
var ThemeContext = /* @__PURE__ */ createContext(
|
|
29
|
+
/* @__PURE__ */ createTheme("light")
|
|
30
|
+
);
|
|
31
|
+
function ThemeProvider(props) {
|
|
32
|
+
const system = useColorScheme();
|
|
33
|
+
const setting = props.colorScheme ?? "system";
|
|
34
|
+
const scheme = setting === "system" ? system === "dark" ? "dark" : "light" : setting;
|
|
35
|
+
const overrides = props.theme;
|
|
36
|
+
const value = useMemo(
|
|
37
|
+
() => createTheme(scheme, overrides),
|
|
38
|
+
[scheme, overrides]
|
|
39
|
+
);
|
|
40
|
+
return /* @__PURE__ */ jsx(ThemeContext.Provider, { value, children: props.children });
|
|
41
|
+
}
|
|
42
|
+
function useTheme() {
|
|
43
|
+
return useContext(ThemeContext);
|
|
44
|
+
}
|
|
45
|
+
function useAgentSweep(runtime) {
|
|
46
|
+
useEffect(() => {
|
|
47
|
+
const subscription = AppState.addEventListener("change", (state) => {
|
|
48
|
+
if (state === "active") runtime.sweep();
|
|
49
|
+
});
|
|
50
|
+
return () => subscription.remove();
|
|
51
|
+
}, [runtime]);
|
|
52
|
+
}
|
|
53
|
+
function useNativeLifecycle(store) {
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
const apply = (state) => store.setForeground(state === "active");
|
|
56
|
+
apply(AppState.currentState);
|
|
57
|
+
const subscription = AppState.addEventListener("change", apply);
|
|
58
|
+
return () => subscription.remove();
|
|
59
|
+
}, [store]);
|
|
60
|
+
}
|
|
61
|
+
function useReducedMotion() {
|
|
62
|
+
const [reduced, setReduced] = useState(false);
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
let live = true;
|
|
65
|
+
AccessibilityInfo.isReduceMotionEnabled().then(
|
|
66
|
+
(value) => {
|
|
67
|
+
if (live) setReduced(value);
|
|
68
|
+
},
|
|
69
|
+
() => void 0
|
|
70
|
+
);
|
|
71
|
+
const subscription = AccessibilityInfo.addEventListener(
|
|
72
|
+
"reduceMotionChanged",
|
|
73
|
+
setReduced
|
|
74
|
+
);
|
|
75
|
+
return () => {
|
|
76
|
+
live = false;
|
|
77
|
+
subscription.remove();
|
|
78
|
+
};
|
|
79
|
+
}, []);
|
|
80
|
+
return reduced;
|
|
81
|
+
}
|
|
82
|
+
function announce(text) {
|
|
83
|
+
if (text) AccessibilityInfo.announceForAccessibility(text);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export { ThemeProvider, announce, createTheme, themeProblems, useAgentSweep, useNativeLifecycle, useReducedMotion, useTheme };
|
|
87
|
+
//# sourceMappingURL=chunk-EFEXTVIT.js.map
|
|
88
|
+
//# sourceMappingURL=chunk-EFEXTVIT.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/theme.tsx","../src/platform.ts"],"names":[],"mappings":"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","file":"chunk-EFEXTVIT.js","sourcesContent":["// The theme: the shared design tokens, a tenant's brand on top, light and dark. A tenant changes colors, fonts, radii\n// and spacing; the disclosure bar keeps a legible pair whatever the tenant's colors (see DisclosureBar).\nimport {\n type ColorScheme,\n type Colors,\n colors,\n contrastProblems,\n font,\n motion,\n radius,\n size,\n space,\n} from '@vinaverse/tokens';\nimport { createContext, type ReactNode, useContext, useMemo } from 'react';\nimport { useColorScheme } from 'react-native';\n\nexport interface Theme {\n readonly scheme: ColorScheme;\n readonly colors: Colors;\n readonly font: typeof font & { readonly family?: string };\n readonly space: typeof space;\n readonly radius: typeof radius;\n readonly motion: typeof motion;\n readonly size: typeof size;\n}\n\n/** A tenant's brand: colors per scheme, a font family, radii. Anything left out keeps the Vinaverse default. */\nexport interface ThemeOverrides {\n readonly light?: Partial<Colors>;\n readonly dark?: Partial<Colors>;\n readonly fontFamily?: string;\n readonly radius?: Partial<typeof radius>;\n readonly space?: Partial<typeof space>;\n}\n\nexport type ColorSchemeSetting = 'system' | 'light' | 'dark';\n\n/** The theme for a scheme, with the tenant's overrides on top. */\nexport function createTheme(\n scheme: ColorScheme,\n overrides: ThemeOverrides = {},\n): Theme {\n return {\n scheme,\n colors: { ...colors(scheme), ...overrides[scheme] },\n font: {\n ...font,\n ...(overrides.fontFamily ? { family: overrides.fontFamily } : {}),\n },\n space: { ...space, ...overrides.space },\n radius: { ...radius, ...overrides.radius },\n motion,\n size,\n };\n}\n\n/** The pairs a tenant's colors made hard to read, in either scheme, as messages for a development warning. */\nexport function themeProblems(overrides: ThemeOverrides = {}): string[] {\n return (['light', 'dark'] as const).flatMap((scheme) =>\n contrastProblems(createTheme(scheme, overrides).colors).map(\n (p) =>\n `${scheme}: ${p.foreground} on ${p.background} is ${p.ratio.toFixed(2)}:1, below ${p.minimum}:1`,\n ),\n );\n}\n\nconst ThemeContext = /* @__PURE__ */ createContext<Theme>(\n /* @__PURE__ */ createTheme('light'),\n);\n\nexport function ThemeProvider(props: {\n readonly theme?: ThemeOverrides | undefined;\n readonly colorScheme?: ColorSchemeSetting | undefined;\n readonly children?: ReactNode;\n}): ReactNode {\n const system = useColorScheme();\n const setting = props.colorScheme ?? 'system';\n const scheme: ColorScheme =\n setting === 'system' ? (system === 'dark' ? 'dark' : 'light') : setting;\n const overrides = props.theme;\n const value = useMemo(\n () => createTheme(scheme, overrides),\n [scheme, overrides],\n );\n return (\n <ThemeContext.Provider value={value}>\n {props.children}\n </ThemeContext.Provider>\n );\n}\n\nexport function useTheme(): Theme {\n return useContext(ThemeContext);\n}\n","// The React Native platform adapter: what the conversation store learns from the app (foreground, background) and\n// what the kit asks of the platform (reduced motion, screen reader announcements).\nimport { useEffect, useState } from 'react';\nimport { AccessibilityInfo, AppState, type AppStateStatus } from 'react-native';\n\n/** The part of the conversation store the adapter drives. */\nexport interface LifecycleTarget {\n setForeground(foreground: boolean): void;\n}\n\n/**\n * When the app comes back to the foreground, the agent runtime ends the conversations no view shows that are past\n * their session's end (timers do not run in the background). Every agent part calls it; once is enough.\n */\nexport function useAgentSweep(runtime: { sweep(): void }): void {\n useEffect(() => {\n const subscription = AppState.addEventListener('change', (state) => {\n if (state === 'active') runtime.sweep();\n });\n return () => subscription.remove();\n }, [runtime]);\n}\n\n/** Tells the store when the app comes to the foreground or goes to the background (it pings only in the foreground). */\nexport function useNativeLifecycle(store: LifecycleTarget): void {\n useEffect(() => {\n const apply = (state: AppStateStatus) =>\n store.setForeground(state === 'active');\n apply(AppState.currentState);\n const subscription = AppState.addEventListener('change', apply);\n return () => subscription.remove();\n }, [store]);\n}\n\n/** Whether the person asked for reduced motion: animations and animated scrolling stay off. */\nexport function useReducedMotion(): boolean {\n const [reduced, setReduced] = useState(false);\n useEffect(() => {\n let live = true;\n AccessibilityInfo.isReduceMotionEnabled().then(\n (value) => {\n if (live) setReduced(value);\n },\n () => undefined,\n );\n const subscription = AccessibilityInfo.addEventListener(\n 'reduceMotionChanged',\n setReduced,\n );\n return () => {\n live = false;\n subscription.remove();\n };\n }, []);\n return reduced;\n}\n\n/** Reads a line to a screen reader, without moving focus. */\nexport function announce(text: string): void {\n if (text) AccessibilityInfo.announceForAccessibility(text);\n}\n"]}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var chunk6QYDPGRS_cjs = require('./chunk-6QYDPGRS.cjs');
|
|
4
|
+
var react = require('@vinaverse/react');
|
|
5
|
+
var agent = require('@vinaverse/react/agent');
|
|
6
|
+
var react$1 = require('react');
|
|
7
|
+
var reactNative = require('react-native');
|
|
8
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
9
|
+
|
|
10
|
+
var LAUNCHER_SIZE = 56;
|
|
11
|
+
function AgentLauncher(props) {
|
|
12
|
+
return /* @__PURE__ */ jsxRuntime.jsx(react.CopyProvider, { locale: props.locale, copy: props.copy, children: /* @__PURE__ */ jsxRuntime.jsx(chunk6QYDPGRS_cjs.ThemeProvider, { theme: props.theme, colorScheme: props.colorScheme, children: /* @__PURE__ */ jsxRuntime.jsx(Launcher, { ...props }) }) });
|
|
13
|
+
}
|
|
14
|
+
function useKeyboardShown() {
|
|
15
|
+
const [shown, setShown] = react$1.useState(false);
|
|
16
|
+
react$1.useEffect(() => {
|
|
17
|
+
const ios = reactNative.Platform.OS === "ios";
|
|
18
|
+
const show = reactNative.Keyboard.addListener(
|
|
19
|
+
ios ? "keyboardWillShow" : "keyboardDidShow",
|
|
20
|
+
() => setShown(true)
|
|
21
|
+
);
|
|
22
|
+
const hide = reactNative.Keyboard.addListener(
|
|
23
|
+
ios ? "keyboardWillHide" : "keyboardDidHide",
|
|
24
|
+
() => setShown(false)
|
|
25
|
+
);
|
|
26
|
+
return () => {
|
|
27
|
+
show.remove();
|
|
28
|
+
hide.remove();
|
|
29
|
+
};
|
|
30
|
+
}, []);
|
|
31
|
+
return shown;
|
|
32
|
+
}
|
|
33
|
+
function Launcher(props) {
|
|
34
|
+
const { t } = react.useCopy();
|
|
35
|
+
const name = agent.useAgentName(t("agentFallbackName"));
|
|
36
|
+
const keyboard = useKeyboardShown();
|
|
37
|
+
const reducedMotion = chunk6QYDPGRS_cjs.useReducedMotion();
|
|
38
|
+
chunk6QYDPGRS_cjs.useAgentSweep(agent.useAgentRuntime());
|
|
39
|
+
const { route = null, routes, mode } = props;
|
|
40
|
+
const visible = props.hidden !== true && !(keyboard && props.hideWithKeyboard !== false) && (!routes || route !== null && routes.includes(route));
|
|
41
|
+
const launcher = agent.useAgentLauncher({
|
|
42
|
+
subject: props.subject,
|
|
43
|
+
mode,
|
|
44
|
+
visible,
|
|
45
|
+
onOpen: () => props.onOpen({ route, mode, entryPoint: "launcher" }),
|
|
46
|
+
...props.storage ? { storage: props.storage } : {},
|
|
47
|
+
...props.storageKey ? { storageKey: props.storageKey } : {},
|
|
48
|
+
...props.delayMs === void 0 ? {} : { delayMs: props.delayMs }
|
|
49
|
+
});
|
|
50
|
+
const text = typeof props.greeting === "function" ? props.greeting(mode) : props.greeting ?? t("launcherGreeting", { agent: name });
|
|
51
|
+
const say = props.announce ?? chunk6QYDPGRS_cjs.announce;
|
|
52
|
+
const said = react$1.useRef(false);
|
|
53
|
+
react$1.useEffect(() => {
|
|
54
|
+
if (!launcher.greets) {
|
|
55
|
+
said.current = false;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (said.current) return;
|
|
59
|
+
said.current = true;
|
|
60
|
+
say(text);
|
|
61
|
+
}, [launcher.greets, text, say]);
|
|
62
|
+
if (!launcher.shows) return null;
|
|
63
|
+
const edge = props.placement?.edge ?? "right";
|
|
64
|
+
const look = props.appearance ?? {};
|
|
65
|
+
const greeting = {
|
|
66
|
+
text,
|
|
67
|
+
closeLabel: t("close"),
|
|
68
|
+
onOpen: launcher.open,
|
|
69
|
+
onClose: launcher.dismiss,
|
|
70
|
+
tail: look.tail !== false,
|
|
71
|
+
edge
|
|
72
|
+
};
|
|
73
|
+
const button = {
|
|
74
|
+
label: t("launcherOpen", { agent: name }),
|
|
75
|
+
name,
|
|
76
|
+
onPress: launcher.open,
|
|
77
|
+
Mark: props.Mark,
|
|
78
|
+
ring: look.ring ?? null,
|
|
79
|
+
shadow: look.shadow !== false
|
|
80
|
+
};
|
|
81
|
+
const { Greeting, Button, Entrance } = props.components ?? {};
|
|
82
|
+
const enter = (part, children) => Entrance ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
83
|
+
Entrance,
|
|
84
|
+
{
|
|
85
|
+
part,
|
|
86
|
+
reducedMotion,
|
|
87
|
+
Default: LauncherEntrance,
|
|
88
|
+
children
|
|
89
|
+
}
|
|
90
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(LauncherEntrance, { part, reducedMotion, children });
|
|
91
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
92
|
+
reactNative.View,
|
|
93
|
+
{
|
|
94
|
+
pointerEvents: "box-none",
|
|
95
|
+
style: [
|
|
96
|
+
{
|
|
97
|
+
position: "absolute",
|
|
98
|
+
bottom: props.placement?.bottom ?? 16,
|
|
99
|
+
[edge]: props.placement?.inset ?? 16,
|
|
100
|
+
alignItems: edge === "right" ? "flex-end" : "flex-start",
|
|
101
|
+
gap: 8
|
|
102
|
+
},
|
|
103
|
+
props.style
|
|
104
|
+
],
|
|
105
|
+
testID: "vinaverse-launcher",
|
|
106
|
+
children: [
|
|
107
|
+
launcher.greets ? enter(
|
|
108
|
+
"greeting",
|
|
109
|
+
Greeting ? /* @__PURE__ */ jsxRuntime.jsx(Greeting, { ...greeting, Default: LauncherGreeting }) : /* @__PURE__ */ jsxRuntime.jsx(LauncherGreeting, { ...greeting })
|
|
110
|
+
) : null,
|
|
111
|
+
enter(
|
|
112
|
+
"button",
|
|
113
|
+
Button ? /* @__PURE__ */ jsxRuntime.jsx(Button, { ...button, Default: LauncherButton }) : /* @__PURE__ */ jsxRuntime.jsx(LauncherButton, { ...button })
|
|
114
|
+
)
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
function LauncherEntrance(props) {
|
|
120
|
+
const { motion } = chunk6QYDPGRS_cjs.useTheme();
|
|
121
|
+
const shown = react$1.useRef(new reactNative.Animated.Value(props.reducedMotion ? 1 : 0)).current;
|
|
122
|
+
react$1.useEffect(() => {
|
|
123
|
+
if (props.reducedMotion) {
|
|
124
|
+
shown.setValue(1);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const run = reactNative.Animated.timing(shown, {
|
|
128
|
+
toValue: 1,
|
|
129
|
+
duration: motion.normal,
|
|
130
|
+
useNativeDriver: true
|
|
131
|
+
});
|
|
132
|
+
run.start();
|
|
133
|
+
return () => run.stop();
|
|
134
|
+
}, [shown, props.reducedMotion, motion.normal]);
|
|
135
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
136
|
+
reactNative.Animated.View,
|
|
137
|
+
{
|
|
138
|
+
pointerEvents: "box-none",
|
|
139
|
+
style: {
|
|
140
|
+
opacity: shown,
|
|
141
|
+
transform: [
|
|
142
|
+
{
|
|
143
|
+
scale: shown.interpolate({
|
|
144
|
+
inputRange: [0, 1],
|
|
145
|
+
outputRange: [0.9, 1]
|
|
146
|
+
})
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
testID: `vinaverse-launcher-entrance-${props.part}`,
|
|
151
|
+
children: props.children
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
function LauncherGreeting(props) {
|
|
156
|
+
const { colors, radius, space, font } = chunk6QYDPGRS_cjs.useTheme();
|
|
157
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
158
|
+
reactNative.View,
|
|
159
|
+
{
|
|
160
|
+
style: {
|
|
161
|
+
maxWidth: 296,
|
|
162
|
+
flexDirection: "row",
|
|
163
|
+
alignItems: "flex-start",
|
|
164
|
+
borderRadius: radius.lg,
|
|
165
|
+
backgroundColor: colors.text
|
|
166
|
+
},
|
|
167
|
+
testID: "vinaverse-launcher-greeting",
|
|
168
|
+
children: [
|
|
169
|
+
props.tail ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
170
|
+
reactNative.View,
|
|
171
|
+
{
|
|
172
|
+
accessible: false,
|
|
173
|
+
style: {
|
|
174
|
+
position: "absolute",
|
|
175
|
+
bottom: -5,
|
|
176
|
+
[props.edge]: LAUNCHER_SIZE / 2 - 6,
|
|
177
|
+
width: 12,
|
|
178
|
+
height: 12,
|
|
179
|
+
backgroundColor: colors.text,
|
|
180
|
+
transform: [{ rotate: "45deg" }]
|
|
181
|
+
},
|
|
182
|
+
testID: "vinaverse-launcher-tail"
|
|
183
|
+
}
|
|
184
|
+
) : null,
|
|
185
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
186
|
+
reactNative.Pressable,
|
|
187
|
+
{
|
|
188
|
+
accessibilityRole: "button",
|
|
189
|
+
accessibilityLabel: props.text,
|
|
190
|
+
onPress: props.onOpen,
|
|
191
|
+
style: {
|
|
192
|
+
flexShrink: 1,
|
|
193
|
+
paddingVertical: space.sm,
|
|
194
|
+
paddingLeft: space.md
|
|
195
|
+
},
|
|
196
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: colors.background, fontSize: font.size.md }, children: props.text })
|
|
197
|
+
}
|
|
198
|
+
),
|
|
199
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
200
|
+
reactNative.Pressable,
|
|
201
|
+
{
|
|
202
|
+
accessibilityRole: "button",
|
|
203
|
+
accessibilityLabel: props.closeLabel,
|
|
204
|
+
onPress: props.onClose,
|
|
205
|
+
style: {
|
|
206
|
+
minWidth: 44,
|
|
207
|
+
minHeight: 44,
|
|
208
|
+
alignItems: "center",
|
|
209
|
+
justifyContent: "center"
|
|
210
|
+
},
|
|
211
|
+
testID: "vinaverse-launcher-close",
|
|
212
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(reactNative.Text, { style: { color: colors.background, fontSize: font.size.md }, children: "\xD7" })
|
|
213
|
+
}
|
|
214
|
+
)
|
|
215
|
+
]
|
|
216
|
+
}
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
function LauncherButton(props) {
|
|
220
|
+
const { colors, font } = chunk6QYDPGRS_cjs.useTheme();
|
|
221
|
+
const { Mark, ring } = props;
|
|
222
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
223
|
+
reactNative.Pressable,
|
|
224
|
+
{
|
|
225
|
+
accessibilityRole: "button",
|
|
226
|
+
accessibilityLabel: props.label,
|
|
227
|
+
onPress: props.onPress,
|
|
228
|
+
style: ({ pressed }) => ({
|
|
229
|
+
width: LAUNCHER_SIZE,
|
|
230
|
+
height: LAUNCHER_SIZE,
|
|
231
|
+
borderRadius: LAUNCHER_SIZE / 2,
|
|
232
|
+
alignItems: "center",
|
|
233
|
+
justifyContent: "center",
|
|
234
|
+
backgroundColor: colors.primary,
|
|
235
|
+
opacity: pressed ? 0.85 : 1,
|
|
236
|
+
...ring ? { borderWidth: ring.width, borderColor: ring.color } : {},
|
|
237
|
+
...props.shadow ? {
|
|
238
|
+
elevation: 6,
|
|
239
|
+
shadowColor: "#000",
|
|
240
|
+
shadowOpacity: 0.2,
|
|
241
|
+
shadowRadius: 8,
|
|
242
|
+
shadowOffset: { width: 0, height: 3 }
|
|
243
|
+
} : {}
|
|
244
|
+
}),
|
|
245
|
+
testID: "vinaverse-launcher-button",
|
|
246
|
+
children: Mark ? /* @__PURE__ */ jsxRuntime.jsx(Mark, { size: 34, color: colors.onPrimary }) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
247
|
+
reactNative.Text,
|
|
248
|
+
{
|
|
249
|
+
style: {
|
|
250
|
+
color: colors.onPrimary,
|
|
251
|
+
fontSize: font.size.lg,
|
|
252
|
+
fontWeight: "600"
|
|
253
|
+
},
|
|
254
|
+
children: (props.name.trim()[0] ?? "?").toUpperCase()
|
|
255
|
+
}
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
exports.AgentLauncher = AgentLauncher;
|
|
262
|
+
exports.LAUNCHER_SIZE = LAUNCHER_SIZE;
|
|
263
|
+
exports.LauncherButton = LauncherButton;
|
|
264
|
+
exports.LauncherEntrance = LauncherEntrance;
|
|
265
|
+
exports.LauncherGreeting = LauncherGreeting;
|
|
266
|
+
exports.useKeyboardShown = useKeyboardShown;
|
|
267
|
+
//# sourceMappingURL=chunk-MG32E4JI.cjs.map
|
|
268
|
+
//# sourceMappingURL=chunk-MG32E4JI.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/AgentLauncher.tsx"],"names":["jsx","CopyProvider","ThemeProvider","useState","useEffect","Platform","Keyboard","useCopy","useAgentName","useReducedMotion","useAgentSweep","useAgentRuntime","useAgentLauncher","announce","useRef","jsxs","View","useTheme","Animated","Pressable","Text"],"mappings":"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","file":"chunk-MG32E4JI.cjs","sourcesContent":["// The agent's floating launcher: a round button over the app's screens and, once per person and mode, a greeting\n// bubble above it, read to screen readers as it appears. Where it shows is a policy: the routes the app allows, never\n// with the keyboard up, never while the app says it should step aside (a sheet, a map). The app places it (`placement`,\n// `style`), gives its brand mark (`Mark`), sets its ring, shadow and the bubble's tail (`appearance`), and can replace\n// the button, the bubble and how they come in (slots, each given the kit's own part; no motion when reduced).\nimport { type CopyOverrides, CopyProvider, useCopy } from '@vinaverse/react';\nimport {\n type GreetingStorage,\n useAgentLauncher,\n useAgentName,\n useAgentRuntime,\n} from '@vinaverse/react/agent';\nimport {\n type ComponentType,\n type ReactNode,\n useEffect,\n useRef,\n useState,\n} from 'react';\nimport {\n Animated,\n Keyboard,\n Platform,\n Pressable,\n type StyleProp,\n Text,\n View,\n type ViewStyle,\n} from 'react-native';\nimport {\n announce as platformAnnounce,\n useAgentSweep,\n useReducedMotion,\n} from '../platform.js';\nimport {\n type ColorSchemeSetting,\n type ThemeOverrides,\n ThemeProvider,\n useTheme,\n} from '../theme.js';\n\n/** The launcher's button side, in dp. */\nexport const LAUNCHER_SIZE = 56;\n\n/** The launcher's look: a ring around the button, its shadow, the bubble's tail toward it. */\nexport interface LauncherAppearance {\n /** Default none. */\n readonly ring?: { readonly color: string; readonly width: number } | null;\n /** Default true. */\n readonly shadow?: boolean;\n /** Default true. */\n readonly tail?: boolean;\n}\n\n/** Where it floats: the screen's edge, its distance from the bottom (above a tab bar) and from the side. */\nexport interface LauncherPlacement {\n /** Default `right`. */\n readonly edge?: 'left' | 'right';\n /** Default 16. */\n readonly bottom?: number;\n /** Default 16. */\n readonly inset?: number;\n}\n\nexport interface LauncherButtonProps {\n readonly label: string;\n readonly name: string;\n readonly onPress: () => void;\n readonly Mark:\n | ComponentType<{ readonly size: number; readonly color: string }>\n | undefined;\n readonly ring: { readonly color: string; readonly width: number } | null;\n readonly shadow: boolean;\n}\nexport interface LauncherGreetingProps {\n readonly text: string;\n readonly closeLabel: string;\n readonly onOpen: () => void;\n readonly onClose: () => void;\n readonly tail: boolean;\n readonly edge: 'left' | 'right';\n}\n/** How the button and the bubble come in; `reducedMotion` when the person asked for none. */\nexport interface LauncherEntranceProps {\n readonly part: 'button' | 'greeting';\n readonly reducedMotion: boolean;\n readonly children: ReactNode;\n}\ntype Slot<P> = ComponentType<P & { readonly Default: ComponentType<P> }>;\n\n/** Where the person opened the agent from: the app's route, the mode, and `entryPoint` to give the chat. */\nexport interface LauncherContext {\n readonly route: string | null;\n readonly mode: string;\n readonly entryPoint: 'launcher';\n}\n\nexport interface AgentLauncherProps {\n readonly subject: string | null;\n readonly mode: string;\n readonly onOpen: (from: LauncherContext) => void;\n /** The app's current route; with `routes`, the launcher shows only on those. */\n readonly route?: string | null;\n readonly routes?: readonly string[];\n /** The app says step aside (a sheet is up, a map with its own cards…). */\n readonly hidden?: boolean;\n /** Hidden while the keyboard is up. Default true. */\n readonly hideWithKeyboard?: boolean;\n /** Where it remembers the greeting was shown (the app's preferences store); default: for this launch only. */\n readonly storage?: GreetingStorage;\n /** The storage's key for a person and mode (a record the app already keeps); default `greetedKey`. */\n readonly storageKey?: (subject: string | null, mode: string) => string;\n /** The greeting's words, or by mode; default the copy's `launcherGreeting`. */\n readonly greeting?: string | ((mode: string) => string);\n /** How long after showing the greeting appears. Default 1200 ms. */\n readonly delayMs?: number;\n /** The app's brand mark inside the button; default the agent name's first letter. */\n readonly Mark?: ComponentType<{\n readonly size: number;\n readonly color: string;\n }>;\n /** Reads the greeting to a screen reader as it appears. Default: the platform's. */\n readonly announce?: (text: string) => void;\n readonly components?: {\n readonly Button?: Slot<LauncherButtonProps>;\n readonly Greeting?: Slot<LauncherGreetingProps>;\n /** Default: a short fade and scale; none with reduced motion. */\n readonly Entrance?: Slot<LauncherEntranceProps>;\n };\n readonly appearance?: LauncherAppearance;\n /** Where it floats over the app's screens; `style` comes after it, for anything else. */\n readonly placement?: LauncherPlacement;\n readonly style?: StyleProp<ViewStyle>;\n /** As on the chat: the person's locale, the app's words, theme and color scheme. */\n readonly locale?: string;\n readonly copy?: CopyOverrides;\n readonly theme?: ThemeOverrides;\n readonly colorScheme?: ColorSchemeSetting;\n}\n\nexport function AgentLauncher(props: AgentLauncherProps): ReactNode {\n return (\n <CopyProvider locale={props.locale} copy={props.copy}>\n <ThemeProvider theme={props.theme} colorScheme={props.colorScheme}>\n <Launcher {...props} />\n </ThemeProvider>\n </CopyProvider>\n );\n}\n\n/** Whether the keyboard is up: on iOS from the moment it starts to rise or fall, on Android (no `Will` events) once it has. */\nexport function useKeyboardShown(): boolean {\n const [shown, setShown] = useState(false);\n useEffect(() => {\n const ios = Platform.OS === 'ios';\n const show = Keyboard.addListener(\n ios ? 'keyboardWillShow' : 'keyboardDidShow',\n () => setShown(true),\n );\n const hide = Keyboard.addListener(\n ios ? 'keyboardWillHide' : 'keyboardDidHide',\n () => setShown(false),\n );\n return () => {\n show.remove();\n hide.remove();\n };\n }, []);\n return shown;\n}\n\nfunction Launcher(props: AgentLauncherProps): ReactNode {\n const { t } = useCopy();\n const name = useAgentName(t('agentFallbackName'));\n const keyboard = useKeyboardShown();\n const reducedMotion = useReducedMotion();\n useAgentSweep(useAgentRuntime());\n const { route = null, routes, mode } = props;\n const visible =\n props.hidden !== true &&\n !(keyboard && props.hideWithKeyboard !== false) &&\n (!routes || (route !== null && routes.includes(route)));\n const launcher = useAgentLauncher({\n subject: props.subject,\n mode,\n visible,\n onOpen: () => props.onOpen({ route, mode, entryPoint: 'launcher' }),\n ...(props.storage ? { storage: props.storage } : {}),\n ...(props.storageKey ? { storageKey: props.storageKey } : {}),\n ...(props.delayMs === undefined ? {} : { delayMs: props.delayMs }),\n });\n const text =\n typeof props.greeting === 'function'\n ? props.greeting(mode)\n : (props.greeting ?? t('launcherGreeting', { agent: name }));\n const say = props.announce ?? platformAnnounce;\n const said = useRef(false);\n useEffect(() => {\n if (!launcher.greets) {\n said.current = false;\n return;\n }\n if (said.current) return;\n said.current = true; // once per greeting, however often the app's `announce` changes\n say(text);\n }, [launcher.greets, text, say]);\n if (!launcher.shows) return null;\n const edge = props.placement?.edge ?? 'right';\n const look = props.appearance ?? {};\n const greeting: LauncherGreetingProps = {\n text,\n closeLabel: t('close'),\n onOpen: launcher.open,\n onClose: launcher.dismiss,\n tail: look.tail !== false,\n edge,\n };\n const button: LauncherButtonProps = {\n label: t('launcherOpen', { agent: name }),\n name,\n onPress: launcher.open,\n Mark: props.Mark,\n ring: look.ring ?? null,\n shadow: look.shadow !== false,\n };\n const { Greeting, Button, Entrance } = props.components ?? {};\n const enter = (part: LauncherEntranceProps['part'], children: ReactNode) =>\n Entrance ? (\n <Entrance\n part={part}\n reducedMotion={reducedMotion}\n Default={LauncherEntrance}\n >\n {children}\n </Entrance>\n ) : (\n <LauncherEntrance part={part} reducedMotion={reducedMotion}>\n {children}\n </LauncherEntrance>\n );\n return (\n <View\n pointerEvents=\"box-none\"\n style={[\n {\n position: 'absolute',\n bottom: props.placement?.bottom ?? 16,\n [edge]: props.placement?.inset ?? 16,\n alignItems: edge === 'right' ? 'flex-end' : 'flex-start',\n gap: 8,\n },\n props.style,\n ]}\n testID=\"vinaverse-launcher\"\n >\n {launcher.greets\n ? enter(\n 'greeting',\n Greeting ? (\n <Greeting {...greeting} Default={LauncherGreeting} />\n ) : (\n <LauncherGreeting {...greeting} />\n ),\n )\n : null}\n {enter(\n 'button',\n Button ? (\n <Button {...button} Default={LauncherButton} />\n ) : (\n <LauncherButton {...button} />\n ),\n )}\n </View>\n );\n}\n\n/** The kit's entrance: a short fade and scale up; none when the person asked for reduced motion. */\nexport function LauncherEntrance(props: LauncherEntranceProps): ReactNode {\n const { motion } = useTheme();\n const shown = useRef(new Animated.Value(props.reducedMotion ? 1 : 0)).current;\n useEffect(() => {\n if (props.reducedMotion) {\n shown.setValue(1);\n return;\n }\n const run = Animated.timing(shown, {\n toValue: 1,\n duration: motion.normal,\n useNativeDriver: true,\n });\n run.start();\n return () => run.stop();\n }, [shown, props.reducedMotion, motion.normal]);\n return (\n <Animated.View\n pointerEvents=\"box-none\"\n style={{\n opacity: shown,\n transform: [\n {\n scale: shown.interpolate({\n inputRange: [0, 1],\n outputRange: [0.9, 1],\n }),\n },\n ],\n }}\n testID={`vinaverse-launcher-entrance-${props.part}`}\n >\n {props.children}\n </Animated.View>\n );\n}\n\n/** The kit's greeting bubble: the words (a tap opens the agent), a close button, and a tail toward the button. */\nexport function LauncherGreeting(props: LauncherGreetingProps): ReactNode {\n const { colors, radius, space, font } = useTheme();\n return (\n <View\n style={{\n maxWidth: 296,\n flexDirection: 'row',\n alignItems: 'flex-start',\n borderRadius: radius.lg,\n backgroundColor: colors.text,\n }}\n testID=\"vinaverse-launcher-greeting\"\n >\n {props.tail ? (\n <View\n accessible={false}\n style={{\n position: 'absolute',\n bottom: -5,\n [props.edge]: LAUNCHER_SIZE / 2 - 6,\n width: 12,\n height: 12,\n backgroundColor: colors.text,\n transform: [{ rotate: '45deg' }],\n }}\n testID=\"vinaverse-launcher-tail\"\n />\n ) : null}\n <Pressable\n accessibilityRole=\"button\"\n accessibilityLabel={props.text}\n onPress={props.onOpen}\n style={{\n flexShrink: 1,\n paddingVertical: space.sm,\n paddingLeft: space.md,\n }}\n >\n <Text style={{ color: colors.background, fontSize: font.size.md }}>\n {props.text}\n </Text>\n </Pressable>\n <Pressable\n accessibilityRole=\"button\"\n accessibilityLabel={props.closeLabel}\n onPress={props.onClose}\n style={{\n minWidth: 44,\n minHeight: 44,\n alignItems: 'center',\n justifyContent: 'center',\n }}\n testID=\"vinaverse-launcher-close\"\n >\n <Text style={{ color: colors.background, fontSize: font.size.md }}>\n ×\n </Text>\n </Pressable>\n </View>\n );\n}\n\n/** The kit's round button: the app's mark, else the agent name's first letter; its ring and shadow as asked. */\nexport function LauncherButton(props: LauncherButtonProps): ReactNode {\n const { colors, font } = useTheme();\n const { Mark, ring } = props;\n return (\n <Pressable\n accessibilityRole=\"button\"\n accessibilityLabel={props.label}\n onPress={props.onPress}\n style={({ pressed }) => ({\n width: LAUNCHER_SIZE,\n height: LAUNCHER_SIZE,\n borderRadius: LAUNCHER_SIZE / 2,\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: colors.primary,\n opacity: pressed ? 0.85 : 1,\n ...(ring ? { borderWidth: ring.width, borderColor: ring.color } : {}),\n ...(props.shadow\n ? {\n elevation: 6,\n shadowColor: '#000',\n shadowOpacity: 0.2,\n shadowRadius: 8,\n shadowOffset: { width: 0, height: 3 },\n }\n : {}),\n })}\n testID=\"vinaverse-launcher-button\"\n >\n {Mark ? (\n <Mark size={34} color={colors.onPrimary} />\n ) : (\n <Text\n style={{\n color: colors.onPrimary,\n fontSize: font.size.lg,\n fontWeight: '600',\n }}\n >\n {(props.name.trim()[0] ?? '?').toUpperCase()}\n </Text>\n )}\n </Pressable>\n );\n}\n"]}
|