@admin-layout/gluestack-ui-mobile 12.2.4-alpha.3 → 12.2.4-alpha.31

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 (49) hide show
  1. package/CHANGELOG.md +36 -8
  2. package/lib/components/ErrorBounday.js.map +1 -1
  3. package/lib/components/Fallback.js +11 -11
  4. package/lib/components/Fallback.js.map +1 -1
  5. package/lib/components/InputToolBar/InputToolBar.d.ts +8 -0
  6. package/lib/components/InputToolBar/InputToolBar.js +219 -0
  7. package/lib/components/InputToolBar/InputToolBar.js.map +1 -0
  8. package/lib/components/InputToolBar/defaults.d.ts +14 -0
  9. package/lib/components/InputToolBar/defaults.js +60 -0
  10. package/lib/components/InputToolBar/defaults.js.map +1 -0
  11. package/lib/components/InputToolBar/index.d.ts +4 -0
  12. package/lib/components/InputToolBar/index.js +3 -0
  13. package/lib/components/InputToolBar/index.js.map +1 -0
  14. package/lib/components/InputToolBar/types.d.ts +153 -0
  15. package/lib/components/InputToolBar/types.js +2 -0
  16. package/lib/components/InputToolBar/types.js.map +1 -0
  17. package/lib/components/Layout/components/BottomTabBar.js +2 -2
  18. package/lib/components/Layout/components/BottomTabBar.js.map +1 -1
  19. package/lib/components/Layout/components/Sample.js.map +1 -1
  20. package/lib/components/ToastAlert.d.ts +1 -1
  21. package/lib/components/ToastAlert.js +2 -2
  22. package/lib/components/ToastAlert.js.map +1 -1
  23. package/lib/components/UnAuthenticatedComponent.js +1 -1
  24. package/lib/components/UnAuthenticatedComponent.js.map +1 -1
  25. package/lib/components/index.d.ts +1 -0
  26. package/lib/components/index.js +1 -0
  27. package/lib/components/index.js.map +1 -1
  28. package/lib/components/usePermissionAutoFetch.js +2 -3
  29. package/lib/components/usePermissionAutoFetch.js.map +1 -1
  30. package/lib/containers/layout/DrawerBottomNavigationConfig.d.ts +63 -303
  31. package/lib/containers/layout/DrawerConfig.d.ts +42 -206
  32. package/lib/containers/layout/ProLayout.js.map +1 -1
  33. package/lib/index.js.map +1 -1
  34. package/package.json +4 -4
  35. package/src/components/ErrorBounday.tsx +19 -19
  36. package/src/components/Fallback.tsx +54 -58
  37. package/src/components/InputToolBar/InputToolBar.tsx +666 -0
  38. package/src/components/InputToolBar/README.md +239 -0
  39. package/src/components/InputToolBar/defaults.ts +72 -0
  40. package/src/components/InputToolBar/index.ts +18 -0
  41. package/src/components/InputToolBar/types.ts +166 -0
  42. package/src/components/Layout/components/BottomTabBar.tsx +98 -99
  43. package/src/components/Layout/components/Sample.tsx +1 -1
  44. package/src/components/ToastAlert.tsx +11 -11
  45. package/src/components/UnAuthenticatedComponent.tsx +16 -26
  46. package/src/components/index.ts +1 -0
  47. package/src/components/usePermissionAutoFetch.tsx +4 -4
  48. package/src/containers/layout/ProLayout.tsx +1 -1
  49. package/src/index.ts +1 -2
@@ -0,0 +1,666 @@
1
+ import React, { useMemo, useState } from 'react';
2
+ import { TextInput, Modal, ScrollView, Pressable, Text as RNText, Dimensions } from 'react-native';
3
+ import { Box, HStack, VStack, Text, Pressable as GluestackPressable } from '@gluestack-ui/themed';
4
+ import { Ionicons, Feather } from '@expo/vector-icons';
5
+ import type {
6
+ InputToolBarClassNames,
7
+ InputToolBarProps,
8
+ RightToolbarItemId,
9
+ LeftToolbarItemId,
10
+ MicSendButtonConfig,
11
+ TemplateButtonConfig,
12
+ TemplateModalConfig,
13
+ TemplateModalItem,
14
+ ToolbarItemConfig,
15
+ } from './types';
16
+
17
+ const iconSize = 14;
18
+ const iconSizeRight = 16;
19
+
20
+ const defaultLeftIconMap: Record<LeftToolbarItemId, React.ReactNode> = {
21
+ search: <Ionicons name="search" size={iconSize} color="currentColor" />,
22
+ zap: <Ionicons name="flash" size={iconSize} color="currentColor" />,
23
+ lightbulb: <Ionicons name="bulb-outline" size={iconSize} color="currentColor" />,
24
+ template: null,
25
+ };
26
+
27
+ const defaultRightIconMap: Record<RightToolbarItemId, React.ReactNode> = {
28
+ projectSettings: <Ionicons name="settings-outline" size={iconSizeRight} color="currentColor" />,
29
+ tag: <Ionicons name="sparkles-outline" size={iconSizeRight} color="currentColor" />,
30
+ chip: <Ionicons name="grid-outline" size={iconSizeRight} color="currentColor" />,
31
+ camera: <Ionicons name="camera-outline" size={iconSizeRight} color="currentColor" />,
32
+ image: <Ionicons name="image-outline" size={iconSizeRight} color="currentColor" />,
33
+ attach: <Ionicons name="attach-outline" size={iconSizeRight} color="currentColor" />,
34
+ mic: <Ionicons name="mic-outline" size={iconSizeRight} color="currentColor" />,
35
+ };
36
+
37
+ function MicSendButton({
38
+ hasContent,
39
+ onSend,
40
+ onMic,
41
+ disabled,
42
+ isLoading,
43
+ onStop,
44
+ classNames,
45
+ }: MicSendButtonConfig & { classNames?: InputToolBarClassNames }) {
46
+ const isStop = isLoading === true;
47
+ const isSend = !isStop && hasContent;
48
+ const handlePress = isStop ? onStop : isSend ? onSend : onMic;
49
+ const label = isStop ? 'Stop' : isSend ? 'Send' : 'Start voice input';
50
+
51
+ return (
52
+ <GluestackPressable
53
+ onPress={handlePress}
54
+ disabled={disabled && !isStop}
55
+ accessibilityLabel={label}
56
+ accessibilityRole="button"
57
+ h={28}
58
+ w={28}
59
+ alignItems="center"
60
+ justifyContent="center"
61
+ rounded="$2xl"
62
+ bg="$black"
63
+ borderWidth={1}
64
+ borderColor="$black"
65
+ >
66
+ {isStop ? (
67
+ <Ionicons name="stop-circle" size={iconSizeRight} color="white" />
68
+ ) : isSend ? (
69
+ <Feather name="arrow-up-right" size={iconSizeRight} color="white" />
70
+ ) : (
71
+ <Ionicons name="mic" size={iconSizeRight} color="white" />
72
+ )}
73
+ </GluestackPressable>
74
+ );
75
+ }
76
+
77
+ function ToolbarIconButton({
78
+ item,
79
+ getDefaultIcon,
80
+ isLeftGroup,
81
+ classNames,
82
+ }: {
83
+ item: ToolbarItemConfig<LeftToolbarItemId | RightToolbarItemId>;
84
+ getDefaultIcon: (id: string) => React.ReactNode;
85
+ isLeftGroup?: boolean;
86
+ classNames?: InputToolBarClassNames;
87
+ }) {
88
+ if (item.enabled === false) return null;
89
+
90
+ const icon = item.customButton ?? item.icon ?? getDefaultIcon(item.id);
91
+ if (item.customButton) {
92
+ return <>{item.customButton}</>;
93
+ }
94
+
95
+ const isDisabled = item.disabled === true || item.loading === true;
96
+ const isActive = item.active === true;
97
+
98
+ return (
99
+ <GluestackPressable
100
+ onPress={item.onClick}
101
+ disabled={isDisabled}
102
+ accessibilityLabel={item.label}
103
+ accessibilityRole="button"
104
+ h={28}
105
+ w={28}
106
+ alignItems="center"
107
+ justifyContent="center"
108
+ rounded={isLeftGroup ? '$full' : '$2xl'}
109
+ borderWidth={1}
110
+ bg={isLeftGroup ? (isActive ? '$black' : '$background100') : 'transparent'}
111
+ borderColor={isLeftGroup ? (isActive ? '$black' : '$outline200') : 'transparent'}
112
+ >
113
+ {item.loading ? (
114
+ <Box
115
+ h={16}
116
+ w={16}
117
+ rounded="$full"
118
+ borderWidth={2}
119
+ borderColor="$primary500"
120
+ borderTopColor="transparent"
121
+ style={{ transform: [{ rotate: '0deg' }] }}
122
+ />
123
+ ) : isActive && isLeftGroup && React.isValidElement(icon) ? (
124
+ React.cloneElement(icon as React.ReactElement<{ color?: string }>, { color: 'white' })
125
+ ) : (
126
+ icon
127
+ )}
128
+ </GluestackPressable>
129
+ );
130
+ }
131
+
132
+ function LeftSection({
133
+ leftItems,
134
+ leftCustomRender,
135
+ classNames,
136
+ }: Pick<InputToolBarProps, 'leftItems' | 'leftCustomRender' | 'classNames'>) {
137
+ if (leftCustomRender != null) {
138
+ return (
139
+ <HStack alignItems="center" gap="$2" flexWrap="wrap">
140
+ {leftCustomRender}
141
+ </HStack>
142
+ );
143
+ }
144
+
145
+ const left = leftItems ?? [];
146
+ const leftEnabled = left.filter((item) => item.enabled !== false);
147
+ if (leftEnabled.length === 0) return null;
148
+
149
+ return (
150
+ <HStack alignItems="center" gap="$1" rounded="$full" bg="$background200" px="$2" py="$1">
151
+ {leftEnabled.map((item, index) => (
152
+ <React.Fragment key={item.id}>
153
+ <ToolbarIconButton
154
+ item={item}
155
+ getDefaultIcon={(id) => defaultLeftIconMap[id as LeftToolbarItemId] ?? null}
156
+ isLeftGroup
157
+ classNames={classNames}
158
+ />
159
+ {index < leftEnabled.length - 1 && <Box w="$px" h={20} bg="$outline200" mx="$1" />}
160
+ </React.Fragment>
161
+ ))}
162
+ </HStack>
163
+ );
164
+ }
165
+
166
+ function TemplatePill({
167
+ label,
168
+ count,
169
+ selectedLabel,
170
+ onClick,
171
+ onClearTemplate,
172
+ disabled,
173
+ classNames,
174
+ }: {
175
+ label: string;
176
+ count?: number;
177
+ selectedLabel?: string | null;
178
+ onClick?: () => void;
179
+ onClearTemplate?: () => void;
180
+ disabled?: boolean;
181
+ classNames?: InputToolBarClassNames;
182
+ }) {
183
+ if (selectedLabel) {
184
+ return (
185
+ <HStack
186
+ alignItems="center"
187
+ gap="$1"
188
+ px="$2"
189
+ py="$1"
190
+ rounded="$full"
191
+ bg="$primary50"
192
+ borderWidth={1}
193
+ borderColor="$primary200"
194
+ >
195
+ <RNText className="text-xs font-medium text-primary-600 truncate max-w-[120px]" numberOfLines={1}>
196
+ {selectedLabel}
197
+ </RNText>
198
+ <GluestackPressable
199
+ onPress={onClearTemplate}
200
+ p="$0.5"
201
+ rounded="$full"
202
+ disabled={disabled}
203
+ accessibilityLabel="Remove template"
204
+ >
205
+ <Ionicons name="close" size={12} color="currentColor" />
206
+ </GluestackPressable>
207
+ <GluestackPressable
208
+ onPress={onClick}
209
+ p="$0.5"
210
+ rounded="$full"
211
+ disabled={disabled}
212
+ accessibilityLabel="Change template"
213
+ >
214
+ <Text fontSize={10}>▼</Text>
215
+ </GluestackPressable>
216
+ </HStack>
217
+ );
218
+ }
219
+ const text = count != null && count > 0 ? `${label} (${count})` : label;
220
+ return (
221
+ <GluestackPressable
222
+ onPress={onClick}
223
+ disabled={disabled}
224
+ accessibilityLabel="Select template"
225
+ opacity={disabled ? 0.5 : 1}
226
+ >
227
+ <HStack
228
+ alignItems="center"
229
+ gap="$1"
230
+ rounded="$full"
231
+ borderWidth={1}
232
+ borderColor="$outline200"
233
+ bg="$background100"
234
+ px="$2"
235
+ py="$1"
236
+ >
237
+ <Text fontSize="$xs" fontWeight="$medium" color="$textLight900">
238
+ {text}
239
+ </Text>
240
+ </HStack>
241
+ </GluestackPressable>
242
+ );
243
+ }
244
+
245
+ function RightSection({
246
+ rightItems,
247
+ rightCustomRender,
248
+ micSendButton,
249
+ classNames,
250
+ }: Pick<InputToolBarProps, 'rightItems' | 'rightCustomRender' | 'micSendButton' | 'classNames'>) {
251
+ if (rightCustomRender != null) {
252
+ return (
253
+ <HStack alignItems="center" gap="$1">
254
+ {rightCustomRender}
255
+ </HStack>
256
+ );
257
+ }
258
+
259
+ const right = rightItems ?? [];
260
+ const rightEnabled = right.filter((item) => item.enabled !== false);
261
+ const filteredRight = micSendButton != null ? rightEnabled.filter((item) => item.id !== 'mic') : rightEnabled;
262
+
263
+ return (
264
+ <HStack alignItems="center" gap="$2" flexWrap="nowrap">
265
+ {filteredRight.map((item) => (
266
+ <ToolbarIconButton
267
+ key={item.id}
268
+ item={item}
269
+ getDefaultIcon={(id) => defaultRightIconMap[id as RightToolbarItemId] ?? null}
270
+ classNames={classNames}
271
+ />
272
+ ))}
273
+ </HStack>
274
+ );
275
+ }
276
+
277
+ function DefaultTemplateModal({
278
+ isOpen,
279
+ onClose,
280
+ templates,
281
+ selectedId,
282
+ onSelect,
283
+ suggestedId,
284
+ title = 'Choose Template',
285
+ }: TemplateModalConfig) {
286
+ const [searchQuery, setSearchQuery] = useState('');
287
+ const [categoryFilter, setCategoryFilter] = useState<string>('All');
288
+
289
+ const categories = useMemo(() => {
290
+ const cats = new Set(templates.map((t) => t.category || 'Other'));
291
+ return ['All', ...Array.from(cats).sort()];
292
+ }, [templates]);
293
+
294
+ const filteredTemplates = useMemo(() => {
295
+ return templates.filter((template) => {
296
+ const searchLower = searchQuery.toLowerCase();
297
+ const matchesSearch =
298
+ !searchQuery ||
299
+ template.label.toLowerCase().includes(searchLower) ||
300
+ (template.description || '').toLowerCase().includes(searchLower);
301
+ const matchesCategory = categoryFilter === 'All' || template.category === categoryFilter;
302
+ return matchesSearch && matchesCategory;
303
+ });
304
+ }, [templates, searchQuery, categoryFilter]);
305
+
306
+ if (!isOpen) return null;
307
+
308
+ const modalMinHeight = Dimensions.get('window').height * 0.5;
309
+
310
+ return (
311
+ <Modal visible={isOpen} transparent animationType="fade">
312
+ <Pressable
313
+ style={{ flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.5)' }}
314
+ onPress={onClose}
315
+ >
316
+ <Pressable
317
+ style={{
318
+ width: '90%',
319
+ maxWidth: 720,
320
+ minHeight: modalMinHeight,
321
+ maxHeight: '80%',
322
+ backgroundColor: 'white',
323
+ borderRadius: 12,
324
+ overflow: 'hidden',
325
+ borderWidth: 1,
326
+ borderColor: '#e4e4e7',
327
+ shadowColor: '#000',
328
+ shadowOffset: { width: 0, height: 4 },
329
+ shadowOpacity: 0.15,
330
+ shadowRadius: 12,
331
+ elevation: 8,
332
+ }}
333
+ onPress={() => {}}
334
+ >
335
+ <VStack flex={1} style={{ minHeight: modalMinHeight }}>
336
+ <HStack
337
+ h={48}
338
+ px="$4"
339
+ borderBottomWidth={1}
340
+ borderBottomColor="#e4e4e7"
341
+ alignItems="center"
342
+ justifyContent="space-between"
343
+ >
344
+ <Text fontSize="$sm" fontWeight="$semibold" color="$textLight900">
345
+ {title}
346
+ </Text>
347
+ <GluestackPressable onPress={onClose} p="$1.5" rounded="$lg" accessibilityLabel="Close">
348
+ <Ionicons name="close" size={16} color="#71717a" />
349
+ </GluestackPressable>
350
+ </HStack>
351
+ <VStack p="$4" borderBottomWidth={1} borderBottomColor="#e4e4e7" gap="$3">
352
+ <Box position="relative">
353
+ <Box
354
+ position="absolute"
355
+ left={12}
356
+ top="50%"
357
+ style={{ transform: [{ translateY: -8 }] }}
358
+ >
359
+ <Ionicons name="search" size={16} color="#71717a" />
360
+ </Box>
361
+ <TextInput
362
+ style={{
363
+ width: '100%',
364
+ paddingLeft: 36,
365
+ paddingRight: 12,
366
+ paddingVertical: 8,
367
+ borderWidth: 1,
368
+ borderColor: '#e4e4e7',
369
+ borderRadius: 8,
370
+ backgroundColor: '#f4f4f5',
371
+ fontSize: 14,
372
+ }}
373
+ placeholder="Search templates..."
374
+ placeholderTextColor="#71717a"
375
+ value={searchQuery}
376
+ onChangeText={setSearchQuery}
377
+ />
378
+ </Box>
379
+ <HStack gap="$1.5" flexWrap="wrap">
380
+ {categories.map((category) => (
381
+ <GluestackPressable
382
+ key={category}
383
+ onPress={() => setCategoryFilter(category)}
384
+ px="$2.5"
385
+ py="$1"
386
+ rounded="$md"
387
+ borderWidth={1}
388
+ bg={categoryFilter === category ? '$primary50' : '$white'}
389
+ borderColor={categoryFilter === category ? '$primary200' : '#e4e4e7'}
390
+ >
391
+ <Text
392
+ fontSize="$xs"
393
+ fontWeight="$medium"
394
+ color={categoryFilter === category ? '$primary600' : '#71717a'}
395
+ >
396
+ {category}
397
+ </Text>
398
+ </GluestackPressable>
399
+ ))}
400
+ </HStack>
401
+ </VStack>
402
+ <Box flex={1} p="$4" style={{ minHeight: 200 }}>
403
+ {filteredTemplates.length > 0 ? (
404
+ <ScrollView
405
+ style={{ flex: 1 }}
406
+ contentContainerStyle={{ paddingBottom: 16 }}
407
+ showsVerticalScrollIndicator={false}
408
+ >
409
+ <HStack flexWrap="wrap" gap="$3">
410
+ {filteredTemplates.map((template) => {
411
+ const isSelected = selectedId === template.id;
412
+ const isSuggested = suggestedId === template.id;
413
+ return (
414
+ <GluestackPressable
415
+ key={template.id}
416
+ onPress={() => {
417
+ onSelect(template.id);
418
+ onClose();
419
+ }}
420
+ p="$3"
421
+ rounded="$lg"
422
+ borderWidth={2}
423
+ borderColor={
424
+ isSelected
425
+ ? '$primary500'
426
+ : isSuggested
427
+ ? 'rgba(59, 130, 246, 0.3)'
428
+ : '#e4e4e7'
429
+ }
430
+ bg={
431
+ isSelected
432
+ ? '$primary50'
433
+ : isSuggested
434
+ ? 'rgba(59, 130, 246, 0.05)'
435
+ : 'transparent'
436
+ }
437
+ style={{ width: '48%' }}
438
+ >
439
+ <HStack alignItems="flex-start" gap="$2">
440
+ <Box
441
+ p="$1.5"
442
+ rounded="$md"
443
+ bg={
444
+ isSelected
445
+ ? '$primary500'
446
+ : isSuggested
447
+ ? '$primary200'
448
+ : '$background200'
449
+ }
450
+ >
451
+ <Ionicons
452
+ name="grid-outline"
453
+ size={14}
454
+ color={
455
+ isSelected
456
+ ? 'white'
457
+ : isSuggested
458
+ ? '#3b82f6'
459
+ : '#18181b'
460
+ }
461
+ />
462
+ </Box>
463
+ <VStack flex={1} style={{ minWidth: 0 }}>
464
+ <HStack alignItems="center" gap="$1.5">
465
+ <Text
466
+ fontSize="$sm"
467
+ fontWeight="$medium"
468
+ color="$textLight900"
469
+ numberOfLines={1}
470
+ >
471
+ {template.label}
472
+ </Text>
473
+ {isSelected && (
474
+ <Ionicons
475
+ name="checkmark"
476
+ size={14}
477
+ color="#3b82f6"
478
+ />
479
+ )}
480
+ </HStack>
481
+ {template.description && (
482
+ <Text
483
+ fontSize="$xs"
484
+ color="$textLight500"
485
+ mt="$0.5"
486
+ numberOfLines={2}
487
+ >
488
+ {template.description}
489
+ </Text>
490
+ )}
491
+ </VStack>
492
+ </HStack>
493
+ </GluestackPressable>
494
+ );
495
+ })}
496
+ </HStack>
497
+ </ScrollView>
498
+ ) : (
499
+ <VStack flex={1} alignItems="center" justifyContent="center" py="$12">
500
+ <Ionicons name="search" size={40} color="#71717a" style={{ marginBottom: 12 }} />
501
+ <Text fontSize="$sm" fontWeight="$medium" color="$textLight900">
502
+ No templates found
503
+ </Text>
504
+ <Text fontSize="$xs" color="$textLight500" mt="$1">
505
+ Try adjusting search or filters
506
+ </Text>
507
+ </VStack>
508
+ )}
509
+ </Box>
510
+ </VStack>
511
+ </Pressable>
512
+ </Pressable>
513
+ </Modal>
514
+ );
515
+ }
516
+
517
+ /**
518
+ * InputToolBar – UI only. Renders toolbar and optional built-in textarea from props.
519
+ * All data (leftItems, rightItems, templateButton, inputConfig, micSendButton) and behavior (onClick, onChange, etc.) come from props.
520
+ */
521
+ export function InputToolBar({
522
+ className,
523
+ classNames,
524
+ inputConfig,
525
+ topContent,
526
+ leftItems: leftItemsProp,
527
+ rightItems: rightItemsProp,
528
+ templateButton,
529
+ templateModalConfig,
530
+ templateModalRender,
531
+ projectSettingsModalOpen,
532
+ onProjectSettingsModalClose,
533
+ projectSettingsModalRender,
534
+ leftCustomRender,
535
+ rightCustomRender,
536
+ micSendButton,
537
+ children,
538
+ onContainerClick,
539
+ }: InputToolBarProps) {
540
+ const leftItems = leftItemsProp ?? [];
541
+ const rightItems = rightItemsProp ?? [];
542
+ const hasInputContent = inputConfig != null || children != null;
543
+ const effectiveTopContent = inputConfig != null ? topContent : null;
544
+
545
+ const templateModal =
546
+ templateModalConfig != null && templateModalConfig.isOpen
547
+ ? templateModalRender != null
548
+ ? templateModalRender(templateModalConfig)
549
+ : React.createElement(DefaultTemplateModal, templateModalConfig)
550
+ : null;
551
+
552
+ const projectSettingsModal =
553
+ projectSettingsModalOpen === true && onProjectSettingsModalClose != null && projectSettingsModalRender != null
554
+ ? projectSettingsModalRender({ onClose: onProjectSettingsModalClose })
555
+ : null;
556
+
557
+ const containerContent = (
558
+ <Box
559
+ w="100%"
560
+ overflow="visible"
561
+ rounded="$3xl"
562
+ bg="$white"
563
+ shadowColor="$black"
564
+ shadowOffset={{ width: 0, height: 4 }}
565
+ shadowOpacity={0.08}
566
+ shadowRadius={12}
567
+ elevation={4}
568
+ >
569
+ <VStack
570
+ px="$3"
571
+ py="$2"
572
+ gap={hasInputContent ? '$2' : '$3'}
573
+ flexDirection={hasInputContent ? 'column' : undefined}
574
+ >
575
+ {hasInputContent && (
576
+ <VStack style={{ minWidth: 0 }} gap="$2">
577
+ {effectiveTopContent}
578
+ {inputConfig != null ? (
579
+ <TextInput
580
+ ref={inputConfig.inputRef}
581
+ value={inputConfig.value}
582
+ onChange={inputConfig.onChange}
583
+ placeholder={inputConfig.placeholder ?? 'Ask anything...'}
584
+ placeholderTextColor="#71717a"
585
+ editable={!inputConfig.disabled}
586
+ multiline
587
+ numberOfLines={1}
588
+ style={{
589
+ width: '100%',
590
+ minHeight: 44,
591
+ fontSize: 16,
592
+ color: '#18181b',
593
+ paddingVertical: 8,
594
+ paddingHorizontal: 0,
595
+ margin: 0,
596
+ maxHeight: 120,
597
+ textAlignVertical: 'top',
598
+ }}
599
+ />
600
+ ) : (
601
+ children
602
+ )}
603
+ </VStack>
604
+ )}
605
+ <VStack gap="$2" pt={hasInputContent ? '$2' : 0}>
606
+ <HStack flexWrap="nowrap" alignItems="center" gap="$3" w="100%">
607
+ <LeftSection
608
+ leftItems={leftItems}
609
+ leftCustomRender={leftCustomRender}
610
+ classNames={classNames}
611
+ />
612
+ <Box flex={1} style={{ minWidth: 8 }} />
613
+ <RightSection
614
+ rightItems={rightItems}
615
+ rightCustomRender={rightCustomRender}
616
+ micSendButton={micSendButton}
617
+ classNames={classNames}
618
+ />
619
+ {micSendButton != null && rightCustomRender == null && (
620
+ <Box style={{ flexShrink: 0 }} ml="$2">
621
+ <MicSendButton
622
+ hasContent={micSendButton.hasContent}
623
+ onSend={micSendButton.onSend}
624
+ onMic={micSendButton.onMic}
625
+ disabled={micSendButton.disabled}
626
+ isLoading={micSendButton.isLoading}
627
+ onStop={micSendButton.onStop}
628
+ classNames={classNames}
629
+ />
630
+ </Box>
631
+ )}
632
+ </HStack>
633
+ {templateButton != null && (
634
+ <HStack alignItems="center" w="100%">
635
+ <TemplatePill
636
+ label={templateButton.label}
637
+ count={templateButton.count}
638
+ selectedLabel={templateButton.selectedLabel}
639
+ onClick={templateButton.onClick}
640
+ onClearTemplate={templateButton.onClearTemplate}
641
+ disabled={templateButton.disabled}
642
+ classNames={classNames}
643
+ />
644
+ </HStack>
645
+ )}
646
+ </VStack>
647
+ </VStack>
648
+ </Box>
649
+ );
650
+
651
+ return (
652
+ <>
653
+ {templateModal}
654
+ {projectSettingsModal}
655
+ {hasInputContent && onContainerClick ? (
656
+ <Pressable onPress={onContainerClick} style={{ width: '100%' }}>
657
+ {containerContent}
658
+ </Pressable>
659
+ ) : (
660
+ containerContent
661
+ )}
662
+ </>
663
+ );
664
+ }
665
+
666
+ export default InputToolBar;