@opengeni/react-native 1.4.5-canary.37615713321001

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +74 -0
  3. package/dist/adapters.d.ts +53 -0
  4. package/dist/adapters.js +3 -0
  5. package/dist/adapters.js.map +1 -0
  6. package/dist/attachments.d.ts +75 -0
  7. package/dist/chunk-3VSZAQ7G.js +391 -0
  8. package/dist/chunk-3VSZAQ7G.js.map +1 -0
  9. package/dist/chunk-5ZVK7WKA.js +88 -0
  10. package/dist/chunk-5ZVK7WKA.js.map +1 -0
  11. package/dist/chunk-EEDIMY3L.js +240 -0
  12. package/dist/chunk-EEDIMY3L.js.map +1 -0
  13. package/dist/chunk-M23SDI4R.js +83 -0
  14. package/dist/chunk-M23SDI4R.js.map +1 -0
  15. package/dist/chunk-PHJBSAR3.js +2006 -0
  16. package/dist/chunk-PHJBSAR3.js.map +1 -0
  17. package/dist/environment.d.ts +19 -0
  18. package/dist/expo.d.ts +25 -0
  19. package/dist/expo.js +267 -0
  20. package/dist/expo.js.map +1 -0
  21. package/dist/index.d.ts +13 -0
  22. package/dist/index.js +3345 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/presentation.d.ts +262 -0
  25. package/dist/read-acknowledgement.d.ts +39 -0
  26. package/dist/realtime/call-events.d.ts +11 -0
  27. package/dist/realtime/call-view.d.ts +26 -0
  28. package/dist/realtime/expo-call.d.ts +11 -0
  29. package/dist/realtime/native-call.d.ts +104 -0
  30. package/dist/realtime/native-realtime.d.ts +35 -0
  31. package/dist/realtime/use-native-session-realtime.d.ts +25 -0
  32. package/dist/session-view.d.ts +87 -0
  33. package/dist/timeline/activity.d.ts +34 -0
  34. package/dist/timeline/composer.d.ts +86 -0
  35. package/dist/timeline/controls.d.ts +27 -0
  36. package/dist/timeline/decisions.d.ts +29 -0
  37. package/dist/timeline/feedback.d.ts +26 -0
  38. package/dist/timeline/icon-nodes.generated.d.ts +738 -0
  39. package/dist/timeline/icon.d.ts +11 -0
  40. package/dist/timeline/index.d.ts +32 -0
  41. package/dist/timeline/index.js +5419 -0
  42. package/dist/timeline/index.js.map +1 -0
  43. package/dist/timeline/markdown.d.ts +11 -0
  44. package/dist/timeline/markdown.js +415 -0
  45. package/dist/timeline/markdown.js.map +1 -0
  46. package/dist/timeline/message-attachments.d.ts +13 -0
  47. package/dist/timeline/message-timeline.d.ts +36 -0
  48. package/dist/timeline/model-picker.d.ts +17 -0
  49. package/dist/timeline/primitives.d.ts +85 -0
  50. package/dist/timeline/queue-dock.d.ts +19 -0
  51. package/dist/timeline/session-actions.d.ts +34 -0
  52. package/dist/timeline/session-list.d.ts +50 -0
  53. package/dist/timeline/session-screen.d.ts +35 -0
  54. package/dist/timeline/session-signals.d.ts +37 -0
  55. package/dist/timeline/sheet.d.ts +14 -0
  56. package/dist/timeline/theme.d.ts +58 -0
  57. package/dist/timeline/thinking-orb.d.ts +13 -0
  58. package/dist/timeline/turn.d.ts +80 -0
  59. package/dist/ui/attention.d.ts +14 -0
  60. package/dist/ui/cards.d.ts +21 -0
  61. package/dist/ui/composer.d.ts +21 -0
  62. package/dist/ui/format.d.ts +13 -0
  63. package/dist/ui/icons.d.ts +9 -0
  64. package/dist/ui/index.d.ts +12 -0
  65. package/dist/ui/index.js +1757 -0
  66. package/dist/ui/index.js.map +1 -0
  67. package/dist/ui/steps-sheet.d.ts +10 -0
  68. package/dist/ui/theme.d.ts +63 -0
  69. package/dist/ui/timeline.d.ts +48 -0
  70. package/dist/ui/web-tokens.gen.d.ts +90 -0
  71. package/dist/use-native-session.d.ts +40 -0
  72. package/dist/view-model.d.ts +52 -0
  73. package/dist/voice-input.d.ts +102 -0
  74. package/dist/webrtc.d.ts +9 -0
  75. package/dist/webrtc.js +19 -0
  76. package/dist/webrtc.js.map +1 -0
  77. package/expo-module.config.json +7 -0
  78. package/ios/OpenGeniCall.podspec +22 -0
  79. package/ios/OpenGeniCallAppDelegateSubscriber.swift +29 -0
  80. package/ios/OpenGeniCallCenter.swift +189 -0
  81. package/ios/OpenGeniCallModule.swift +64 -0
  82. package/package.json +93 -0
  83. package/src/adapters.ts +150 -0
  84. package/src/attachments.ts +396 -0
  85. package/src/environment.tsx +214 -0
  86. package/src/expo.ts +295 -0
  87. package/src/index.ts +98 -0
  88. package/src/presentation.ts +285 -0
  89. package/src/read-acknowledgement.ts +72 -0
  90. package/src/realtime/call-events.ts +41 -0
  91. package/src/realtime/call-view.tsx +220 -0
  92. package/src/realtime/expo-call.ts +49 -0
  93. package/src/realtime/native-call.ts +266 -0
  94. package/src/realtime/native-realtime.ts +115 -0
  95. package/src/realtime/use-native-session-realtime.ts +29 -0
  96. package/src/session-view.tsx +2251 -0
  97. package/src/timeline/activity.tsx +736 -0
  98. package/src/timeline/composer.tsx +686 -0
  99. package/src/timeline/controls.tsx +151 -0
  100. package/src/timeline/decisions.tsx +651 -0
  101. package/src/timeline/feedback.tsx +251 -0
  102. package/src/timeline/icon-nodes.generated.ts +1830 -0
  103. package/src/timeline/icon.tsx +44 -0
  104. package/src/timeline/index.ts +67 -0
  105. package/src/timeline/markdown.tsx +510 -0
  106. package/src/timeline/message-attachments.tsx +155 -0
  107. package/src/timeline/message-timeline.tsx +1042 -0
  108. package/src/timeline/model-picker.tsx +450 -0
  109. package/src/timeline/primitives.tsx +561 -0
  110. package/src/timeline/queue-dock.tsx +331 -0
  111. package/src/timeline/session-actions.tsx +379 -0
  112. package/src/timeline/session-list.tsx +338 -0
  113. package/src/timeline/session-screen.tsx +488 -0
  114. package/src/timeline/session-signals.tsx +312 -0
  115. package/src/timeline/sheet.tsx +153 -0
  116. package/src/timeline/theme.tsx +128 -0
  117. package/src/timeline/thinking-orb.tsx +93 -0
  118. package/src/timeline/turn.tsx +569 -0
  119. package/src/ui/attention.tsx +220 -0
  120. package/src/ui/cards.tsx +267 -0
  121. package/src/ui/composer.tsx +296 -0
  122. package/src/ui/format.ts +86 -0
  123. package/src/ui/icons.tsx +131 -0
  124. package/src/ui/index.ts +18 -0
  125. package/src/ui/steps-sheet.tsx +71 -0
  126. package/src/ui/theme.ts +261 -0
  127. package/src/ui/timeline.tsx +837 -0
  128. package/src/ui/web-tokens.gen.ts +94 -0
  129. package/src/use-native-session.ts +219 -0
  130. package/src/view-model.ts +485 -0
  131. package/src/voice-input.ts +382 -0
  132. package/src/webrtc.ts +30 -0
@@ -0,0 +1,220 @@
1
+ import type { PendingApproval, PendingHumanInputRequest } from "@opengeni/react/session";
2
+ import { toolDisplayName } from "@opengeni/react/session";
3
+ import type { HumanInputAnswer } from "@opengeni/sdk";
4
+ import { useState } from "react";
5
+ import { Pressable, ScrollView, StyleSheet, Text, View } from "react-native";
6
+ import Animated, { FadeInDown, FadeOutDown, LinearTransition } from "react-native-reanimated";
7
+ import { ActionButton } from "./cards";
8
+ import { toolDetail } from "./format";
9
+ import { AgentIcon } from "./icons";
10
+ import type { AgentTheme } from "./theme";
11
+
12
+ type Attention =
13
+ | { kind: "approval"; approval: PendingApproval }
14
+ | { kind: "question"; request: PendingHumanInputRequest };
15
+
16
+ function approvalSummary(approval: PendingApproval): string | null {
17
+ return toolDetail({
18
+ kind: "tool-call",
19
+ id: approval.id,
20
+ turnId: null,
21
+ callId: null,
22
+ name: approval.name,
23
+ arguments: approval.arguments,
24
+ output: null,
25
+ raw: null,
26
+ status: "running",
27
+ occurredAt: "",
28
+ });
29
+ }
30
+
31
+ /**
32
+ * Everything the agent is waiting on, docked directly above the composer. One item at a
33
+ * time with a counter; decisions never scroll away with the conversation.
34
+ */
35
+ export function AgentAttentionTray(props: {
36
+ approvals: readonly PendingApproval[];
37
+ humanInput: readonly PendingHumanInputRequest[];
38
+ theme: AgentTheme;
39
+ onApproval(approvalId: string, decision: "approve" | "reject"): void;
40
+ onAnswer(requestId: string, answers: HumanInputAnswer[] | null): void;
41
+ }) {
42
+ const { theme } = props;
43
+ const items: Attention[] = [
44
+ ...props.approvals.map((approval) => ({ kind: "approval" as const, approval })),
45
+ ...props.humanInput.map((request) => ({ kind: "question" as const, request })),
46
+ ];
47
+ const [selected, setSelected] = useState<string[]>([]);
48
+ const current = items[0];
49
+ if (!current) return null;
50
+ const key = current.kind === "approval" ? `a:${current.approval.id}` : `q:${current.request.id}`;
51
+ const question = current.kind === "question" ? current.request.questions[0] : undefined;
52
+ const multi = question?.kind === "multi_select";
53
+
54
+ return (
55
+ <Animated.View
56
+ entering={FadeInDown.duration(220)}
57
+ exiting={FadeOutDown.duration(160)}
58
+ key={key}
59
+ layout={LinearTransition.duration(180)}
60
+ style={{
61
+ marginHorizontal: 12,
62
+ marginBottom: 6,
63
+ borderRadius: theme.radius.card,
64
+ backgroundColor: theme.colors.surfaceRaised,
65
+ borderWidth: StyleSheet.hairlineWidth * 2,
66
+ borderColor: theme.colors.attention,
67
+ padding: 14,
68
+ gap: 10,
69
+ shadowColor: "#000",
70
+ shadowOpacity: theme.scheme === "light" ? 0.1 : 0,
71
+ shadowRadius: 18,
72
+ shadowOffset: { width: 0, height: 6 },
73
+ }}
74
+ >
75
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
76
+ <AgentIcon
77
+ color={theme.colors.attention}
78
+ name={current.kind === "approval" ? "alert" : "question"}
79
+ size={15}
80
+ />
81
+ <Text
82
+ style={{
83
+ flex: 1,
84
+ color: theme.colors.attention,
85
+ fontSize: theme.type.small,
86
+ fontWeight: "600",
87
+ }}
88
+ >
89
+ {current.kind === "approval" ? "Approve to continue" : "Answer to continue"}
90
+ </Text>
91
+ {items.length > 1 ? (
92
+ <Text style={{ color: theme.colors.textFaint, fontSize: theme.type.small }}>
93
+ 1 of {items.length}
94
+ </Text>
95
+ ) : null}
96
+ </View>
97
+ {current.kind === "approval" ? (
98
+ <>
99
+ <View>
100
+ <Text
101
+ style={{
102
+ color: theme.colors.text,
103
+ fontSize: theme.type.body,
104
+ fontWeight: theme.type.weightStrong,
105
+ }}
106
+ >
107
+ {toolDisplayName(current.approval.name, current.approval.display)}
108
+ </Text>
109
+ {approvalSummary(current.approval) ? (
110
+ <Text
111
+ numberOfLines={2}
112
+ style={{
113
+ marginTop: 2,
114
+ color: theme.colors.textMuted,
115
+ fontSize: theme.type.small,
116
+ lineHeight: theme.type.smallLine,
117
+ }}
118
+ >
119
+ {approvalSummary(current.approval)}
120
+ </Text>
121
+ ) : null}
122
+ </View>
123
+ <View style={{ flexDirection: "row", gap: 10 }}>
124
+ <ActionButton
125
+ flex
126
+ label="Deny"
127
+ onPress={() => props.onApproval(current.approval.id, "reject")}
128
+ theme={theme}
129
+ variant="secondary"
130
+ />
131
+ <ActionButton
132
+ flex
133
+ label="Approve"
134
+ onPress={() => props.onApproval(current.approval.id, "approve")}
135
+ theme={theme}
136
+ variant="primary"
137
+ />
138
+ </View>
139
+ </>
140
+ ) : question ? (
141
+ <>
142
+ <Text
143
+ style={{
144
+ color: theme.colors.text,
145
+ fontSize: theme.type.body,
146
+ fontWeight: theme.type.weightStrong,
147
+ }}
148
+ >
149
+ {question.prompt}
150
+ </Text>
151
+ <ScrollView
152
+ contentContainerStyle={{ gap: 8 }}
153
+ horizontal
154
+ keyboardShouldPersistTaps="handled"
155
+ showsHorizontalScrollIndicator={false}
156
+ >
157
+ {question.options.map((option) => {
158
+ const active = selected.includes(option.id);
159
+ return (
160
+ <Pressable
161
+ accessibilityRole={multi ? "checkbox" : "radio"}
162
+ accessibilityState={{ checked: active }}
163
+ key={option.id}
164
+ onPress={() =>
165
+ setSelected((values) =>
166
+ multi
167
+ ? values.includes(option.id)
168
+ ? values.filter((value) => value !== option.id)
169
+ : [...values, option.id]
170
+ : [option.id],
171
+ )
172
+ }
173
+ style={{
174
+ minHeight: theme.layout.target - 4,
175
+ paddingHorizontal: 14,
176
+ borderRadius: theme.radius.control,
177
+ justifyContent: "center",
178
+ backgroundColor: active ? theme.colors.accent : theme.colors.surface,
179
+ }}
180
+ >
181
+ <Text
182
+ style={{
183
+ color: active ? theme.colors.onAccent : theme.colors.text,
184
+ fontSize: theme.type.body - 1,
185
+ fontWeight: "600",
186
+ }}
187
+ >
188
+ {option.label}
189
+ </Text>
190
+ </Pressable>
191
+ );
192
+ })}
193
+ </ScrollView>
194
+ <View style={{ flexDirection: "row", gap: 10 }}>
195
+ {current.request.allowSkip ? (
196
+ <ActionButton
197
+ flex
198
+ label="Skip"
199
+ onPress={() => props.onAnswer(current.request.id, null)}
200
+ theme={theme}
201
+ variant="secondary"
202
+ />
203
+ ) : null}
204
+ <ActionButton
205
+ disabled={selected.length === 0}
206
+ flex
207
+ label="Send"
208
+ onPress={() => {
209
+ props.onAnswer(current.request.id, [{ questionId: question.id, values: selected }]);
210
+ setSelected([]);
211
+ }}
212
+ theme={theme}
213
+ variant="primary"
214
+ />
215
+ </View>
216
+ </>
217
+ ) : null}
218
+ </Animated.View>
219
+ );
220
+ }
@@ -0,0 +1,267 @@
1
+ import type { PendingApproval, PendingHumanInputRequest } from "@opengeni/react/session";
2
+ import { toolDisplayName } from "@opengeni/react/session";
3
+ import type { HumanInputAnswer } from "@opengeni/sdk";
4
+ import { useState } from "react";
5
+ import { Pressable, StyleSheet, Text, View } from "react-native";
6
+ import { AgentIcon } from "./icons";
7
+ import type { AgentTheme } from "./theme";
8
+
9
+ function argumentRows(args: unknown): [string, string][] {
10
+ if (!args || typeof args !== "object") return [];
11
+ return Object.entries(args as Record<string, unknown>)
12
+ .slice(0, 4)
13
+ .map(([key, value]) => [key, typeof value === "string" ? value : JSON.stringify(value)]);
14
+ }
15
+
16
+ export function ActionButton(props: {
17
+ label: string;
18
+ onPress(): void;
19
+ theme: AgentTheme;
20
+ variant: "primary" | "secondary" | "danger";
21
+ disabled?: boolean;
22
+ flex?: boolean;
23
+ }) {
24
+ const { theme, variant } = props;
25
+ const primary = variant === "primary";
26
+ return (
27
+ <Pressable
28
+ accessibilityRole="button"
29
+ accessibilityState={{ disabled: !!props.disabled }}
30
+ disabled={props.disabled}
31
+ onPress={props.onPress}
32
+ style={({ pressed }) => ({
33
+ flex: props.flex ? 1 : undefined,
34
+ minHeight: theme.layout.target,
35
+ paddingHorizontal: 18,
36
+ borderRadius: theme.radius.control,
37
+ alignItems: "center",
38
+ justifyContent: "center",
39
+ backgroundColor: primary ? theme.colors.accent : "transparent",
40
+ borderWidth: primary ? 0 : StyleSheet.hairlineWidth * 2,
41
+ borderColor: variant === "danger" ? theme.colors.danger : theme.colors.border,
42
+ opacity: props.disabled ? 0.4 : pressed ? 0.75 : 1,
43
+ })}
44
+ >
45
+ <Text
46
+ style={{
47
+ color: primary
48
+ ? theme.colors.onAccent
49
+ : variant === "danger"
50
+ ? theme.colors.danger
51
+ : theme.colors.text,
52
+ fontSize: theme.type.body - 1,
53
+ fontWeight: theme.type.weightStrong,
54
+ }}
55
+ >
56
+ {props.label}
57
+ </Text>
58
+ </Pressable>
59
+ );
60
+ }
61
+
62
+ function CardShell(props: {
63
+ theme: AgentTheme;
64
+ icon: "alert" | "question";
65
+ eyebrow: string;
66
+ title: string;
67
+ children: React.ReactNode;
68
+ }) {
69
+ const { theme } = props;
70
+ return (
71
+ <View
72
+ style={{
73
+ marginVertical: theme.space.row,
74
+ borderRadius: theme.radius.card,
75
+ backgroundColor: theme.colors.surfaceRaised,
76
+ borderWidth: StyleSheet.hairlineWidth * 2,
77
+ borderColor: theme.colors.attention,
78
+ padding: 16,
79
+ gap: 12,
80
+ shadowColor: "#000",
81
+ shadowOpacity: theme.scheme === "light" ? 0.06 : 0,
82
+ shadowRadius: 12,
83
+ shadowOffset: { width: 0, height: 4 },
84
+ }}
85
+ >
86
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
87
+ <AgentIcon color={theme.colors.attention} name={props.icon} size={16} />
88
+ <Text
89
+ style={{
90
+ color: theme.colors.attention,
91
+ fontSize: theme.type.small,
92
+ fontWeight: "600",
93
+ letterSpacing: 0.2,
94
+ }}
95
+ >
96
+ {props.eyebrow}
97
+ </Text>
98
+ </View>
99
+ <Text
100
+ style={{
101
+ color: theme.colors.text,
102
+ fontSize: theme.type.body + 1,
103
+ lineHeight: theme.type.bodyLine + 1,
104
+ fontWeight: theme.type.weightStrong,
105
+ }}
106
+ >
107
+ {props.title}
108
+ </Text>
109
+ {props.children}
110
+ </View>
111
+ );
112
+ }
113
+
114
+ export function ApprovalCard(props: {
115
+ approval: PendingApproval;
116
+ theme: AgentTheme;
117
+ onDecision(decision: "approve" | "reject"): void;
118
+ }) {
119
+ const { approval, theme } = props;
120
+ const rows = argumentRows(approval.arguments);
121
+ return (
122
+ <CardShell
123
+ eyebrow="Needs your approval"
124
+ icon="alert"
125
+ theme={theme}
126
+ title={toolDisplayName(approval.name, approval.display)}
127
+ >
128
+ {rows.length ? (
129
+ <View
130
+ style={{
131
+ borderRadius: theme.radius.control,
132
+ backgroundColor: theme.colors.codeBackground,
133
+ padding: 10,
134
+ gap: 4,
135
+ }}
136
+ >
137
+ {rows.map(([key, value]) => (
138
+ <View key={key} style={{ flexDirection: "row", gap: 8 }}>
139
+ <Text
140
+ style={{
141
+ color: theme.colors.textFaint,
142
+ fontSize: theme.type.small,
143
+ fontFamily: theme.type.mono,
144
+ }}
145
+ >
146
+ {key}
147
+ </Text>
148
+ <Text
149
+ numberOfLines={2}
150
+ style={{
151
+ flex: 1,
152
+ color: theme.colors.text,
153
+ fontSize: theme.type.small,
154
+ fontFamily: theme.type.mono,
155
+ }}
156
+ >
157
+ {value}
158
+ </Text>
159
+ </View>
160
+ ))}
161
+ </View>
162
+ ) : null}
163
+ <View style={{ flexDirection: "row", gap: 10 }}>
164
+ <ActionButton
165
+ flex
166
+ label="Deny"
167
+ onPress={() => props.onDecision("reject")}
168
+ theme={theme}
169
+ variant="secondary"
170
+ />
171
+ <ActionButton
172
+ flex
173
+ label="Approve"
174
+ onPress={() => props.onDecision("approve")}
175
+ theme={theme}
176
+ variant="primary"
177
+ />
178
+ </View>
179
+ </CardShell>
180
+ );
181
+ }
182
+
183
+ export function QuestionCard(props: {
184
+ request: PendingHumanInputRequest;
185
+ theme: AgentTheme;
186
+ onAnswer(answers: HumanInputAnswer[] | null): void;
187
+ }) {
188
+ const { request, theme } = props;
189
+ const question = request.questions[0];
190
+ const [selected, setSelected] = useState<string[]>([]);
191
+ if (!question) return null;
192
+ const multi = question.kind === "multi_select";
193
+ const toggle = (id: string) =>
194
+ setSelected((current) =>
195
+ multi
196
+ ? current.includes(id)
197
+ ? current.filter((value) => value !== id)
198
+ : [...current, id]
199
+ : [id],
200
+ );
201
+ return (
202
+ <CardShell eyebrow="Question for you" icon="question" theme={theme} title={question.prompt}>
203
+ <View style={{ gap: 8 }}>
204
+ {question.options.map((option) => {
205
+ const active = selected.includes(option.id);
206
+ return (
207
+ <Pressable
208
+ accessibilityRole={multi ? "checkbox" : "radio"}
209
+ accessibilityState={{ checked: active }}
210
+ key={option.id}
211
+ onPress={() => toggle(option.id)}
212
+ style={{
213
+ minHeight: theme.layout.target,
214
+ borderRadius: theme.radius.control,
215
+ borderWidth: active ? 2 : StyleSheet.hairlineWidth * 2,
216
+ borderColor: active ? theme.colors.accent : theme.colors.border,
217
+ paddingHorizontal: 14,
218
+ paddingVertical: 10,
219
+ justifyContent: "center",
220
+ }}
221
+ >
222
+ <Text
223
+ style={{
224
+ color: theme.colors.text,
225
+ fontSize: theme.type.body - 1,
226
+ fontWeight: "500",
227
+ }}
228
+ >
229
+ {option.label}
230
+ </Text>
231
+ {option.description ? (
232
+ <Text
233
+ style={{
234
+ color: theme.colors.textMuted,
235
+ fontSize: theme.type.small,
236
+ marginTop: 2,
237
+ }}
238
+ >
239
+ {option.description}
240
+ </Text>
241
+ ) : null}
242
+ </Pressable>
243
+ );
244
+ })}
245
+ </View>
246
+ <View style={{ flexDirection: "row", gap: 10 }}>
247
+ {request.allowSkip ? (
248
+ <ActionButton
249
+ flex
250
+ label="Skip"
251
+ onPress={() => props.onAnswer(null)}
252
+ theme={theme}
253
+ variant="secondary"
254
+ />
255
+ ) : null}
256
+ <ActionButton
257
+ disabled={selected.length === 0}
258
+ flex
259
+ label="Send answer"
260
+ onPress={() => props.onAnswer([{ questionId: question.id, values: selected }])}
261
+ theme={theme}
262
+ variant="primary"
263
+ />
264
+ </View>
265
+ </CardShell>
266
+ );
267
+ }