@alia.onl/sdk 2.0.0 → 3.0.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.
@@ -0,0 +1,164 @@
1
+ import * as React from "react";
2
+ import {
3
+ TextInput,
4
+ Platform,
5
+ View,
6
+ type NativeSyntheticEvent,
7
+ type TextInputKeyPressEventData,
8
+ type NativeSyntheticEvent as RNSyntheticEvent,
9
+ type TextInputContentSizeChangeEventData,
10
+ } from "react-native";
11
+ import { cn } from "../../lib/utils";
12
+
13
+ type ChatTextInputProps = React.ComponentPropsWithoutRef<typeof TextInput> & {
14
+ noFocus?: boolean;
15
+ onEnterPress?: () => void;
16
+ onCompletionKey?: (key: string) => boolean;
17
+ maxHeight?: number;
18
+ minHeight?: number;
19
+ onHeightChange?: (height: number) => void;
20
+ disableEnterToSubmit?: boolean;
21
+ disableAutoHeight?: boolean;
22
+ onImagePaste?: (files: File[]) => void;
23
+ fillContainer?: boolean;
24
+ };
25
+
26
+ const ChatTextInput = React.forwardRef<TextInput, ChatTextInputProps>(
27
+ ({
28
+ className,
29
+ noFocus = false,
30
+ onEnterPress,
31
+ onCompletionKey,
32
+ onKeyPress,
33
+ maxHeight = 200,
34
+ minHeight = 44,
35
+ onContentSizeChange,
36
+ onHeightChange,
37
+ style,
38
+ disableEnterToSubmit = false,
39
+ disableAutoHeight = false,
40
+ onImagePaste,
41
+ fillContainer = false,
42
+ ...props
43
+ }, ref) => {
44
+ const inputRef = React.useRef<TextInput>(null);
45
+ const wrapperRef = React.useRef<View>(null);
46
+
47
+ React.useImperativeHandle(ref, () => inputRef.current as TextInput);
48
+
49
+ // Attach paste event listener (web only)
50
+ React.useEffect(() => {
51
+ if (Platform.OS !== 'web' || !onImagePaste) return;
52
+
53
+ const handlePaste = (e: Event) => {
54
+ const clipboardEvent = e as ClipboardEvent;
55
+
56
+ // @ts-ignore - web-specific API
57
+ const activeElement = document.activeElement;
58
+ // @ts-ignore - web-specific API
59
+ const wrapper = wrapperRef.current;
60
+
61
+ // @ts-ignore - web-specific API
62
+ const isContained = wrapper && wrapper.contains(activeElement);
63
+
64
+ if (!isContained) return;
65
+
66
+ const items = clipboardEvent.clipboardData?.items;
67
+ if (!items) return;
68
+
69
+ const imageFiles: File[] = [];
70
+ for (let i = 0; i < items.length; i++) {
71
+ const item = items[i];
72
+ if (item.type.indexOf('image') !== -1) {
73
+ const file = item.getAsFile();
74
+ if (file) {
75
+ imageFiles.push(file);
76
+ }
77
+ }
78
+ }
79
+
80
+ if (imageFiles.length > 0) {
81
+ clipboardEvent.preventDefault();
82
+ onImagePaste(imageFiles);
83
+ }
84
+ };
85
+
86
+ // @ts-ignore - web-specific API
87
+ document.addEventListener('paste', handlePaste);
88
+
89
+ return () => {
90
+ // @ts-ignore - web-specific API
91
+ document.removeEventListener('paste', handlePaste);
92
+ };
93
+ }, [onImagePaste]);
94
+
95
+ const handleKeyPress = (
96
+ e: NativeSyntheticEvent<TextInputKeyPressEventData>
97
+ ) => {
98
+ onKeyPress?.(e);
99
+
100
+ const key = e.nativeEvent.key;
101
+
102
+ if (onCompletionKey && (key === "ArrowUp" || key === "ArrowDown" || key === "Enter" || key === "Escape")) {
103
+ if (onCompletionKey(key)) {
104
+ e.preventDefault();
105
+ return;
106
+ }
107
+ }
108
+
109
+ if (key === "Enter" && !disableEnterToSubmit) {
110
+ // @ts-ignore - shiftKey exists on web
111
+ if (Platform.OS !== 'web' || !e.nativeEvent.shiftKey) {
112
+ e.preventDefault();
113
+ onEnterPress?.();
114
+ }
115
+ }
116
+ };
117
+
118
+ const handleContentSizeChange = (
119
+ e: RNSyntheticEvent<TextInputContentSizeChangeEventData>
120
+ ) => {
121
+ onContentSizeChange?.(e);
122
+ };
123
+
124
+ return (
125
+ <View
126
+ ref={wrapperRef}
127
+ style={{ width: '100%', ...(fillContainer && { flex: 1 }) }}
128
+ onLayout={(e) => onHeightChange?.(e.nativeEvent.layout.height)}
129
+ >
130
+ <TextInput
131
+ ref={inputRef}
132
+ className={cn(
133
+ "native:text-md native:leading-[1.25] rounded-xl border border-input bg-background px-3.5 text-base text-foreground file:border-0 file:bg-transparent file:font-medium placeholder:text-muted-foreground web:flex web:w-full web:py-2 lg:text-sm",
134
+ "web:ring-offset-background web:focus-visible:outline-none web:focus-visible:ring-2 web:focus-visible:ring-ring web:focus-visible:ring-offset-2",
135
+ !fillContainer && !props.multiline && "h-9",
136
+ fillContainer && "h-full",
137
+ noFocus && "web:focus-visible:ring-0 web:focus-visible:ring-offset-0",
138
+ props.editable === false && "opacity-50 web:cursor-not-allowed",
139
+ className
140
+ )}
141
+ placeholderClassName={cn("text-muted-foreground", props.placeholderClassName)}
142
+ onKeyPress={handleKeyPress}
143
+ onContentSizeChange={handleContentSizeChange}
144
+ scrollEnabled={fillContainer || props.multiline}
145
+ style={[
146
+ style,
147
+ !fillContainer && props.multiline && !disableAutoHeight && {
148
+ minHeight,
149
+ maxHeight,
150
+ overflow: 'auto' as any,
151
+ ...(Platform.OS === 'web' ? { fieldSizing: 'content' } : {}),
152
+ },
153
+ fillContainer && { flex: 1, height: '100%' },
154
+ ]}
155
+ {...props}
156
+ />
157
+ </View>
158
+ );
159
+ }
160
+ );
161
+
162
+ ChatTextInput.displayName = "ChatTextInput";
163
+
164
+ export { ChatTextInput };
@@ -0,0 +1,2 @@
1
+ // Native: re-export zeego's native context menus
2
+ export * from "zeego/dropdown-menu";
@@ -0,0 +1,355 @@
1
+ // Web dropdown menu using Radix UI with Tailwind styling.
2
+ // Provides the same zeego-compatible API as the native dropdown-menu.tsx.
3
+ import { cn } from "../../lib/utils";
4
+ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
5
+ import { CheckIcon, ChevronRightIcon } from "@radix-ui/react-icons";
6
+ import * as React from "react";
7
+ import {
8
+ Star, Pencil, Trash2, Share2, Download, Settings, HelpCircle,
9
+ Image, FileText, Search, ShoppingBag, MoreHorizontal, ExternalLink,
10
+ BookOpen, Globe, PenTool, Sparkles, User, CreditCard, Bell, LogOut,
11
+ Folder, Check, Brain, Ghost, Bot, Bookmark, AlertTriangle, Pin,
12
+ } from "lucide-react-native";
13
+
14
+ // Map iOS SF Symbol names to Lucide icons for web rendering
15
+ const SF_SYMBOL_MAP: Record<string, React.ComponentType<any>> = {
16
+ "star": Star, "star.fill": Star, "pencil": Pencil, "trash": Trash2,
17
+ "square.and.arrow.up": Share2, "arrow.down.doc": Download,
18
+ "gearshape": Settings, "questionmark.circle": HelpCircle,
19
+ "photo": Image, "doc": FileText, "magnifyingglass": Search,
20
+ "bag": ShoppingBag, "ellipsis": MoreHorizontal, "link": ExternalLink,
21
+ "book": BookOpen, "globe": Globe, "pencil.tip": PenTool,
22
+ "sparkle": Sparkles, "person.circle": User, "creditcard": CreditCard,
23
+ "bell": Bell, "rectangle.portrait.and.arrow.right": LogOut,
24
+ "folder": Folder, "checkmark": Check, "brain": Brain,
25
+ "eye.slash": Ghost, "cpu": Bot,
26
+ "bookmark": Bookmark, "bookmark.fill": Bookmark,
27
+ "exclamationmark.triangle": AlertTriangle,
28
+ "pin": Pin, "pin.fill": Pin,
29
+ };
30
+
31
+
32
+ // Singleton registry: only one dropdown menu open at a time.
33
+ const openMenus = new Set<() => void>();
34
+
35
+ function DropdownMenu({
36
+ open: controlledOpen,
37
+ onOpenChange,
38
+ defaultOpen,
39
+ ...props
40
+ }: React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Root>) {
41
+ const [internalOpen, setInternalOpen] = React.useState(defaultOpen ?? false);
42
+ const open = controlledOpen ?? internalOpen;
43
+
44
+ const onOpenChangeRef = React.useRef(onOpenChange);
45
+ onOpenChangeRef.current = onOpenChange;
46
+
47
+ const closeRef = React.useRef<() => void>(undefined);
48
+ if (!closeRef.current) {
49
+ closeRef.current = () => {
50
+ setInternalOpen(false);
51
+ onOpenChangeRef.current?.(false);
52
+ };
53
+ }
54
+
55
+ React.useEffect(() => {
56
+ const cb = closeRef.current!;
57
+ openMenus.add(cb);
58
+ return () => { openMenus.delete(cb); };
59
+ }, []);
60
+
61
+ const handleOpenChange = (next: boolean) => {
62
+ if (next) {
63
+ openMenus.forEach((cb) => {
64
+ if (cb !== closeRef.current) cb();
65
+ });
66
+ }
67
+ setInternalOpen(next);
68
+ onOpenChange?.(next);
69
+ };
70
+
71
+ return (
72
+ <DropdownMenuPrimitive.Root
73
+ modal={false}
74
+ open={open}
75
+ onOpenChange={handleOpenChange}
76
+ {...props}
77
+ />
78
+ );
79
+ }
80
+
81
+ const DropdownMenuTrigger = React.forwardRef<
82
+ React.ElementRef<typeof DropdownMenuPrimitive.Trigger>,
83
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Trigger>
84
+ >((props, ref) => (
85
+ <DropdownMenuPrimitive.Trigger ref={ref} asChild {...props} />
86
+ ));
87
+ DropdownMenuTrigger.displayName = "DropdownMenuTrigger";
88
+
89
+ const DropdownMenuSub = DropdownMenuPrimitive.Sub;
90
+
91
+ const DropdownMenuGroup = React.forwardRef<
92
+ React.ElementRef<typeof DropdownMenuPrimitive.Group>,
93
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Group> & {
94
+ horizontal?: boolean;
95
+ }
96
+ >(({ className, horizontal, children, ...props }, ref) => (
97
+ <DropdownMenuPrimitive.Group
98
+ ref={ref}
99
+ className={cn(
100
+ horizontal && "flex flex-row space-x-2 justify-between",
101
+ className
102
+ )}
103
+ {...props}
104
+ >
105
+ {children}
106
+ </DropdownMenuPrimitive.Group>
107
+ ));
108
+ DropdownMenuGroup.displayName = DropdownMenuPrimitive.Group.displayName;
109
+
110
+ const DropdownMenuSubTrigger = React.forwardRef<
111
+ React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
112
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
113
+ inset?: boolean;
114
+ }
115
+ >(({ className, inset, children, ...props }, ref) => (
116
+ <DropdownMenuPrimitive.SubTrigger
117
+ ref={ref}
118
+ className={cn(
119
+ "flex cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground [&_svg:not([class*='text-'])]:text-muted-foreground [&:focus_svg:not([class*='text-'])]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
120
+ inset && "pl-8",
121
+ className
122
+ )}
123
+ {...props}
124
+ >
125
+ {children}
126
+ <ChevronRightIcon className="ml-auto h-4 w-4" />
127
+ </DropdownMenuPrimitive.SubTrigger>
128
+ ));
129
+ DropdownMenuSubTrigger.displayName =
130
+ DropdownMenuPrimitive.SubTrigger.displayName;
131
+
132
+ const DropdownMenuSubContent = React.forwardRef<
133
+ React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
134
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
135
+ >(({ className, style, ...props }, ref) => (
136
+ <DropdownMenuPrimitive.Portal>
137
+ <DropdownMenuPrimitive.SubContent
138
+ ref={ref}
139
+ className={cn(
140
+ "z-[9999] min-w-[96px] rounded-md bg-popover p-1 text-popover-foreground duration-100 origin-[var(--radix-dropdown-menu-content-transform-origin)] overflow-hidden data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
141
+ className
142
+ )}
143
+ style={{
144
+ boxShadow: '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)',
145
+ border: '1px solid hsl(var(--border))',
146
+ ...style as any,
147
+ }}
148
+ {...props}
149
+ />
150
+ </DropdownMenuPrimitive.Portal>
151
+ ));
152
+ DropdownMenuSubContent.displayName =
153
+ DropdownMenuPrimitive.SubContent.displayName;
154
+
155
+ const DropdownMenuContent = React.forwardRef<
156
+ React.ElementRef<typeof DropdownMenuPrimitive.Content>,
157
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
158
+ >(({ className, sideOffset = 4, align = "start", style, ...props }, ref) => (
159
+ <DropdownMenuPrimitive.Portal>
160
+ <DropdownMenuPrimitive.Content
161
+ ref={ref}
162
+ sideOffset={sideOffset}
163
+ align={align}
164
+ className={cn(
165
+ "z-[9999] min-w-[8rem] rounded-lg bg-popover p-1 text-popover-foreground duration-100 max-h-[var(--radix-dropdown-menu-content-available-height)] origin-[var(--radix-dropdown-menu-content-transform-origin)] overflow-x-hidden overflow-y-auto data-[state=closed]:overflow-hidden",
166
+ "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
167
+ className
168
+ )}
169
+ style={{
170
+ boxShadow: '0 4px 6px -1px rgba(0,0,0,0.1), 0 2px 4px -2px rgba(0,0,0,0.1)',
171
+ border: '1px solid hsl(var(--border))',
172
+ ...style as any,
173
+ }}
174
+ {...props}
175
+ />
176
+ </DropdownMenuPrimitive.Portal>
177
+ ));
178
+ DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName;
179
+
180
+ const DropdownMenuItem = React.forwardRef<
181
+ React.ElementRef<typeof DropdownMenuPrimitive.Item>,
182
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
183
+ inset?: boolean;
184
+ destructive?: boolean;
185
+ shouldDismissMenuOnSelect?: boolean;
186
+ }
187
+ >(
188
+ (
189
+ {
190
+ className,
191
+ inset,
192
+ destructive,
193
+ shouldDismissMenuOnSelect,
194
+ onSelect,
195
+ ...props
196
+ },
197
+ ref
198
+ ) => (
199
+ <DropdownMenuPrimitive.Item
200
+ ref={ref}
201
+ onSelect={(e: any) => {
202
+ onSelect?.(e);
203
+ if (shouldDismissMenuOnSelect === false) {
204
+ e.preventDefault();
205
+ }
206
+ }}
207
+ className={cn(
208
+ "relative flex flex-row cursor-default select-none items-center gap-1.5 rounded-md px-1.5 py-1 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg:not([class*='text-'])]:text-muted-foreground [&:focus_svg:not([class*='text-'])]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
209
+ inset && "pl-8",
210
+ destructive &&
211
+ "text-destructive focus:text-destructive focus:bg-destructive/10 dark:focus:bg-destructive/20 [&_svg]:!text-destructive",
212
+ className
213
+ )}
214
+ {...props}
215
+ />
216
+ )
217
+ );
218
+ DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName;
219
+
220
+ const DropdownMenuCheckboxItem = React.forwardRef<
221
+ React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
222
+ Omit<
223
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>,
224
+ "onSelect" | "checked"
225
+ > & {
226
+ value: "mixed" | "on" | "off" | boolean;
227
+ onValueChange?: (
228
+ state: "mixed" | "on" | "off",
229
+ prevState: "mixed" | "on" | "off"
230
+ ) => void;
231
+ shouldDismissMenuOnSelect?: boolean;
232
+ }
233
+ >(
234
+ (
235
+ {
236
+ className,
237
+ value,
238
+ children,
239
+ shouldDismissMenuOnSelect,
240
+ onValueChange,
241
+ ...props
242
+ },
243
+ ref
244
+ ) => (
245
+ <DropdownMenuPrimitive.CheckboxItem
246
+ ref={ref}
247
+ onSelect={(e: any) => {
248
+ const current =
249
+ value === true ? "on" : value === false ? "off" : value;
250
+ const next = current === "on" ? "off" : "on";
251
+ onValueChange?.(next, current);
252
+ if (shouldDismissMenuOnSelect === false) {
253
+ e.preventDefault();
254
+ }
255
+ }}
256
+ className={cn(
257
+ "relative flex cursor-default select-none items-center gap-1.5 rounded-md py-1 pr-8 pl-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground [&:focus_svg:not([class*='text-'])]:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
258
+ className
259
+ )}
260
+ checked={typeof value === "boolean" ? value : value !== "off"}
261
+ {...props}
262
+ >
263
+ <span className="absolute right-2 flex h-3.5 w-3.5 items-center justify-center">
264
+ <DropdownMenuPrimitive.ItemIndicator>
265
+ <CheckIcon className="h-4 w-4" />
266
+ </DropdownMenuPrimitive.ItemIndicator>
267
+ </span>
268
+ {children}
269
+ </DropdownMenuPrimitive.CheckboxItem>
270
+ )
271
+ );
272
+ DropdownMenuCheckboxItem.displayName =
273
+ DropdownMenuPrimitive.CheckboxItem.displayName;
274
+
275
+ const DropdownMenuLabel = React.forwardRef<
276
+ React.ElementRef<typeof DropdownMenuPrimitive.Label>,
277
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
278
+ inset?: boolean;
279
+ }
280
+ >(({ className, inset, ...props }, ref) => (
281
+ <DropdownMenuPrimitive.Label
282
+ ref={ref}
283
+ className={cn(
284
+ "text-muted-foreground px-1.5 py-1 text-xs font-medium",
285
+ inset && "pl-8",
286
+ className
287
+ )}
288
+ {...props}
289
+ />
290
+ ));
291
+ DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName;
292
+
293
+ const DropdownMenuSeparator = React.forwardRef<
294
+ React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
295
+ React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
296
+ >(({ className, ...props }, ref) => (
297
+ <DropdownMenuPrimitive.Separator
298
+ ref={ref}
299
+ className={cn("-mx-1 my-1 h-px bg-border", className)}
300
+ {...props}
301
+ />
302
+ ));
303
+ DropdownMenuSeparator.displayName =
304
+ DropdownMenuPrimitive.Separator.displayName;
305
+
306
+ export const ItemIcon = React.forwardRef<
307
+ HTMLSpanElement,
308
+ React.HTMLProps<HTMLSpanElement> & { ios?: { name?: string; [key: string]: any }; androidIconName?: string }
309
+ >(({ className, ios, androidIconName, children, ...props }, ref) => {
310
+ const IconComponent = ios?.name ? SF_SYMBOL_MAP[ios.name] : null;
311
+ return (
312
+ <span ref={ref} className={cn("inline-flex h-4 w-4 items-center justify-center shrink-0 text-muted-foreground", className)} {...props}>
313
+ {IconComponent ? <IconComponent size={16} /> : children}
314
+ </span>
315
+ );
316
+ });
317
+ ItemIcon.displayName = "ItemIcon";
318
+
319
+ export const ItemTitle = React.forwardRef<
320
+ HTMLSpanElement,
321
+ React.HTMLProps<HTMLSpanElement>
322
+ >((props, ref) => {
323
+ return <span ref={ref} {...props} />;
324
+ });
325
+ ItemTitle.displayName = "ItemTitle";
326
+
327
+ export const ItemSubtitle = React.forwardRef<
328
+ HTMLSpanElement,
329
+ React.HTMLProps<HTMLSpanElement>
330
+ >(({ className, ...props }, ref) => {
331
+ return (
332
+ <span
333
+ ref={ref}
334
+ className={cn("block text-xs text-muted-foreground mt-0.5", className)}
335
+ {...props}
336
+ />
337
+ );
338
+ });
339
+ ItemSubtitle.displayName = "ItemSubtitle";
340
+
341
+ export { ItemImage, ItemIndicator, Arrow } from "zeego/dropdown-menu";
342
+
343
+ export {
344
+ DropdownMenu as Root,
345
+ DropdownMenuTrigger as Trigger,
346
+ DropdownMenuContent as Content,
347
+ DropdownMenuItem as Item,
348
+ DropdownMenuCheckboxItem as CheckboxItem,
349
+ DropdownMenuLabel as Label,
350
+ DropdownMenuSeparator as Separator,
351
+ DropdownMenuGroup as Group,
352
+ DropdownMenuSub as Sub,
353
+ DropdownMenuSubContent as SubContent,
354
+ DropdownMenuSubTrigger as SubTrigger,
355
+ };
@@ -0,0 +1,28 @@
1
+ import React from "react";
2
+ import { View } from "react-native";
3
+ import { cn } from "../../../lib/utils";
4
+ import { usePromptInput } from "./context";
5
+
6
+ export type PromptInputActionsProps = React.ComponentProps<typeof View>;
7
+
8
+ export function PromptInputActions({
9
+ children,
10
+ className,
11
+ ...props
12
+ }: PromptInputActionsProps) {
13
+ const { isFullscreen } = usePromptInput();
14
+
15
+ return (
16
+ <View
17
+ className={cn(
18
+ "flex-row items-center gap-2",
19
+ isFullscreen &&
20
+ "absolute bottom-4 left-4 right-4 max-w-2xl mx-auto rounded-full border border-border bg-background px-4 py-3",
21
+ className
22
+ )}
23
+ {...props}
24
+ >
25
+ {children}
26
+ </View>
27
+ );
28
+ }
@@ -0,0 +1,83 @@
1
+ import React from "react";
2
+ import { Plus } from "lucide-react-native";
3
+ import { cn } from "../../../lib/utils";
4
+ import { Button } from "../button";
5
+ import * as DropdownMenu from "../dropdown-menu";
6
+ import { useImagePicker } from "../../../hooks/useImagePicker";
7
+ import { useDocumentPicker } from "../../../hooks/useDocumentPicker";
8
+ import { usePromptInput } from "./context";
9
+
10
+ export type PromptInputAddMenuProps = {
11
+ className?: string;
12
+ iconSize?: number;
13
+ };
14
+
15
+ export function PromptInputAddMenu({ className, iconSize = 16 }: PromptInputAddMenuProps) {
16
+ const { addAttachment } = usePromptInput();
17
+ const { pickImage } = useImagePicker();
18
+ const { pickDocument } = useDocumentPicker();
19
+
20
+ const handleAddPhotos = async () => {
21
+ try {
22
+ const assets = await pickImage();
23
+ if (assets && assets.length > 0) {
24
+ assets.forEach((asset) => {
25
+ addAttachment({
26
+ id: `img-${Date.now()}-${Math.random().toString(36).slice(2)}`,
27
+ uri: asset.uri,
28
+ type: "image",
29
+ name: asset.name,
30
+ size: asset.size,
31
+ mimeType: asset.mimeType,
32
+ });
33
+ });
34
+ }
35
+ } catch (err) {
36
+ console.error("Error picking images:", err);
37
+ }
38
+ };
39
+
40
+ const handleAddDocument = async () => {
41
+ try {
42
+ const docs = await pickDocument();
43
+ if (docs && docs.length > 0) {
44
+ docs.forEach((doc) => {
45
+ addAttachment({
46
+ id: `doc-${Date.now()}-${Math.random().toString(36).slice(2)}`,
47
+ uri: doc.uri,
48
+ type: "document",
49
+ name: doc.name,
50
+ size: doc.size,
51
+ mimeType: doc.mimeType,
52
+ });
53
+ });
54
+ }
55
+ } catch (err) {
56
+ console.error("Error picking documents:", err);
57
+ }
58
+ };
59
+
60
+ return (
61
+ <DropdownMenu.Root>
62
+ <DropdownMenu.Trigger>
63
+ <Button
64
+ variant="outline"
65
+ size="icon"
66
+ className={cn("h-8 w-8 rounded-full border-0", className)}
67
+ >
68
+ <Plus size={iconSize} className="text-muted-foreground" />
69
+ </Button>
70
+ </DropdownMenu.Trigger>
71
+ <DropdownMenu.Content side="top" align="start">
72
+ <DropdownMenu.Item key="photos" onSelect={handleAddPhotos}>
73
+ <DropdownMenu.ItemIcon ios={{ name: "photo" }} />
74
+ <DropdownMenu.ItemTitle>Add photos</DropdownMenu.ItemTitle>
75
+ </DropdownMenu.Item>
76
+ <DropdownMenu.Item key="document" onSelect={handleAddDocument}>
77
+ <DropdownMenu.ItemIcon ios={{ name: "doc" }} />
78
+ <DropdownMenu.ItemTitle>Add document</DropdownMenu.ItemTitle>
79
+ </DropdownMenu.Item>
80
+ </DropdownMenu.Content>
81
+ </DropdownMenu.Root>
82
+ );
83
+ }