@alia.onl/sdk 3.1.0 → 3.1.1
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/package.json +47 -19
- package/src/components/AliaChatMessageList.tsx +0 -1
- package/src/components/AliaChatScreen.tsx +8 -43
- package/src/components/AliaChatSheet.tsx +8 -26
- package/src/components/AliaFace.tsx +108 -58
- package/src/components/Markdown.tsx +59 -39
- package/src/components/Reasoning.tsx +1 -3
- package/src/components/ResearchProgressCard.tsx +16 -17
- package/src/components/ThinkingIndicator.tsx +6 -21
- package/src/components/voice/AudioWaveVisualizer.tsx +72 -22
- package/src/components/voice/VoiceControls.tsx +25 -42
- package/src/components/voice/VoiceOverlay.tsx +5 -1
- package/src/hooks/useVoiceRoom.ts +24 -8
- package/src/index.ts +2 -2
- package/src/theme.ts +2 -41
- package/src/components/AliaChatInput.tsx +0 -294
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@alia.onl/sdk",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.1",
|
|
4
4
|
"description": "Alia AI chat SDK — reusable chat UI, voice, and streaming components for Oxy apps",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"react-native": "src/index.ts",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"peerDependencies": {
|
|
18
18
|
"react": ">=18",
|
|
19
19
|
"react-native": ">=0.73",
|
|
20
|
-
"@oxyhq/services": "
|
|
20
|
+
"@oxyhq/services": "6.10.7",
|
|
21
21
|
"expo-blur": "*",
|
|
22
22
|
"react-native-reanimated": ">=3",
|
|
23
23
|
"react-native-gesture-handler": ">=2",
|
|
@@ -39,22 +39,50 @@
|
|
|
39
39
|
"@radix-ui/react-dropdown-menu": ">=2"
|
|
40
40
|
},
|
|
41
41
|
"peerDependenciesMeta": {
|
|
42
|
-
"livekit-client": {
|
|
43
|
-
"
|
|
44
|
-
"react-native-svg": { "optional": true },
|
|
45
|
-
"nativewind": { "optional": true },
|
|
46
|
-
"clsx": { "optional": true },
|
|
47
|
-
"tailwind-merge": { "optional": true },
|
|
48
|
-
"class-variance-authority": { "optional": true },
|
|
49
|
-
"zeego": { "optional": true },
|
|
50
|
-
"expo-image": { "optional": true },
|
|
51
|
-
"expo-image-picker": { "optional": true },
|
|
52
|
-
"expo-document-picker": { "optional": true },
|
|
53
|
-
"expo-clipboard": { "optional": true },
|
|
54
|
-
"lucide-react-native": { "optional": true },
|
|
55
|
-
"@radix-ui/react-dropdown-menu": { "optional": true }
|
|
42
|
+
"livekit-client": {
|
|
43
|
+
"optional": true
|
|
56
44
|
},
|
|
57
|
-
"
|
|
58
|
-
|
|
59
|
-
|
|
45
|
+
"expo-audio": {
|
|
46
|
+
"optional": true
|
|
47
|
+
},
|
|
48
|
+
"react-native-svg": {
|
|
49
|
+
"optional": true
|
|
50
|
+
},
|
|
51
|
+
"nativewind": {
|
|
52
|
+
"optional": true
|
|
53
|
+
},
|
|
54
|
+
"clsx": {
|
|
55
|
+
"optional": true
|
|
56
|
+
},
|
|
57
|
+
"tailwind-merge": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"class-variance-authority": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"zeego": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"expo-image": {
|
|
67
|
+
"optional": true
|
|
68
|
+
},
|
|
69
|
+
"expo-image-picker": {
|
|
70
|
+
"optional": true
|
|
71
|
+
},
|
|
72
|
+
"expo-document-picker": {
|
|
73
|
+
"optional": true
|
|
74
|
+
},
|
|
75
|
+
"expo-clipboard": {
|
|
76
|
+
"optional": true
|
|
77
|
+
},
|
|
78
|
+
"lucide-react-native": {
|
|
79
|
+
"optional": true
|
|
80
|
+
},
|
|
81
|
+
"@radix-ui/react-dropdown-menu": {
|
|
82
|
+
"optional": true
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
"devDependencies": {
|
|
86
|
+
"typescript": "^5.9.3"
|
|
87
|
+
}
|
|
60
88
|
}
|
|
@@ -14,11 +14,10 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import React, { useRef, useState, useEffect, useCallback } from 'react';
|
|
17
|
-
import { View, Text, TouchableOpacity
|
|
17
|
+
import { View, Text, TouchableOpacity } from 'react-native';
|
|
18
18
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
19
19
|
import { AliaChatContent, type AliaChatContentRef } from './AliaChatContent';
|
|
20
20
|
import { AliaFace, type AliaExpression } from './AliaFace';
|
|
21
|
-
import { useAliaColors } from '../theme';
|
|
22
21
|
import type { WelcomeSuggestion } from './AliaWelcomeMessage';
|
|
23
22
|
|
|
24
23
|
export interface AliaChatScreenProps {
|
|
@@ -50,7 +49,6 @@ export function AliaChatScreen({
|
|
|
50
49
|
welcomeSubtitle,
|
|
51
50
|
welcomeSuggestions,
|
|
52
51
|
}: AliaChatScreenProps) {
|
|
53
|
-
const colors = useAliaColors();
|
|
54
52
|
const insets = useSafeAreaInsets();
|
|
55
53
|
const contentRef = useRef<AliaChatContentRef>(null);
|
|
56
54
|
|
|
@@ -73,18 +71,18 @@ export function AliaChatScreen({
|
|
|
73
71
|
}, []);
|
|
74
72
|
|
|
75
73
|
return (
|
|
76
|
-
<View style={
|
|
74
|
+
<View className="flex-1 bg-background" style={{ paddingTop: insets.top }}>
|
|
77
75
|
{/* Header */}
|
|
78
|
-
<View
|
|
79
|
-
<View
|
|
76
|
+
<View className="flex-row items-center justify-between px-4 py-3">
|
|
77
|
+
<View className="flex-row items-center gap-2.5">
|
|
80
78
|
{headerLeft}
|
|
81
79
|
<AliaFace size={28} expression={faceExpression} />
|
|
82
|
-
<Text
|
|
80
|
+
<Text className="text-lg font-semibold text-foreground">Alia</Text>
|
|
83
81
|
</View>
|
|
84
|
-
<View
|
|
82
|
+
<View className="flex-row items-center gap-2">
|
|
85
83
|
{hasMessages && (
|
|
86
|
-
<TouchableOpacity onPress={handleClear}
|
|
87
|
-
<Text
|
|
84
|
+
<TouchableOpacity onPress={handleClear} className="px-2.5 py-1.5">
|
|
85
|
+
<Text className="text-sm text-muted-foreground">Clear</Text>
|
|
88
86
|
</TouchableOpacity>
|
|
89
87
|
)}
|
|
90
88
|
{headerRight}
|
|
@@ -105,36 +103,3 @@ export function AliaChatScreen({
|
|
|
105
103
|
);
|
|
106
104
|
}
|
|
107
105
|
|
|
108
|
-
const styles = StyleSheet.create({
|
|
109
|
-
container: {
|
|
110
|
-
flex: 1,
|
|
111
|
-
},
|
|
112
|
-
header: {
|
|
113
|
-
flexDirection: 'row',
|
|
114
|
-
alignItems: 'center',
|
|
115
|
-
justifyContent: 'space-between',
|
|
116
|
-
paddingHorizontal: 16,
|
|
117
|
-
paddingVertical: 12,
|
|
118
|
-
},
|
|
119
|
-
headerLeft: {
|
|
120
|
-
flexDirection: 'row',
|
|
121
|
-
alignItems: 'center',
|
|
122
|
-
gap: 10,
|
|
123
|
-
},
|
|
124
|
-
headerTitle: {
|
|
125
|
-
fontSize: 18,
|
|
126
|
-
fontWeight: '600',
|
|
127
|
-
},
|
|
128
|
-
headerRight: {
|
|
129
|
-
flexDirection: 'row',
|
|
130
|
-
alignItems: 'center',
|
|
131
|
-
gap: 8,
|
|
132
|
-
},
|
|
133
|
-
clearButton: {
|
|
134
|
-
paddingHorizontal: 10,
|
|
135
|
-
paddingVertical: 6,
|
|
136
|
-
},
|
|
137
|
-
clearText: {
|
|
138
|
-
fontSize: 14,
|
|
139
|
-
},
|
|
140
|
-
});
|
|
@@ -16,6 +16,7 @@ import {
|
|
|
16
16
|
Pressable,
|
|
17
17
|
Dimensions,
|
|
18
18
|
Platform,
|
|
19
|
+
useColorScheme,
|
|
19
20
|
} from 'react-native';
|
|
20
21
|
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler';
|
|
21
22
|
import { useKeyboardHandler } from 'react-native-keyboard-controller';
|
|
@@ -29,7 +30,6 @@ import Animated, {
|
|
|
29
30
|
import { useSafeAreaInsets } from 'react-native-safe-area-context';
|
|
30
31
|
import { AliaChatContent, type AliaChatContentRef } from './AliaChatContent';
|
|
31
32
|
import { AliaFace, type AliaExpression } from './AliaFace';
|
|
32
|
-
import { useAliaColors } from '../theme';
|
|
33
33
|
import type { WelcomeSuggestion } from './AliaWelcomeMessage';
|
|
34
34
|
|
|
35
35
|
const { height: SCREEN_HEIGHT } = Dimensions.get('window');
|
|
@@ -62,8 +62,8 @@ export interface AliaChatSheetRef {
|
|
|
62
62
|
|
|
63
63
|
export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
|
|
64
64
|
({ clientContext, model, apiUrl, welcomeGreeting, welcomeSubtitle, welcomeSuggestions }, ref) => {
|
|
65
|
-
const
|
|
66
|
-
const isDark =
|
|
65
|
+
const scheme = useColorScheme();
|
|
66
|
+
const isDark = scheme === 'dark';
|
|
67
67
|
const insets = useSafeAreaInsets();
|
|
68
68
|
|
|
69
69
|
// Content ref for reading state
|
|
@@ -237,9 +237,9 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
|
|
|
237
237
|
{/* Sheet */}
|
|
238
238
|
<GestureDetector gesture={panGesture}>
|
|
239
239
|
<Animated.View
|
|
240
|
+
className="bg-background"
|
|
240
241
|
style={[
|
|
241
242
|
styles.sheet,
|
|
242
|
-
{ backgroundColor: colors.background },
|
|
243
243
|
sheetAnimStyle,
|
|
244
244
|
sheetMaxHeightStyle,
|
|
245
245
|
]}
|
|
@@ -258,19 +258,14 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
|
|
|
258
258
|
<View style={styles.header}>
|
|
259
259
|
<View style={styles.headerLeft}>
|
|
260
260
|
<AliaFace size={28} expression={faceExpression} />
|
|
261
|
-
<Text
|
|
261
|
+
<Text className="text-lg font-semibold text-foreground">
|
|
262
262
|
Alia
|
|
263
263
|
</Text>
|
|
264
264
|
</View>
|
|
265
265
|
<View style={styles.headerRight}>
|
|
266
266
|
{hasMessages && (
|
|
267
267
|
<TouchableOpacity onPress={handleClear} style={styles.clearButton}>
|
|
268
|
-
<Text
|
|
269
|
-
style={[
|
|
270
|
-
styles.clearText,
|
|
271
|
-
{ color: colors.secondaryText },
|
|
272
|
-
]}
|
|
273
|
-
>
|
|
268
|
+
<Text className="text-sm text-muted-foreground">
|
|
274
269
|
Clear
|
|
275
270
|
</Text>
|
|
276
271
|
</TouchableOpacity>
|
|
@@ -280,13 +275,7 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
|
|
|
280
275
|
style={styles.closeButton}
|
|
281
276
|
hitSlop={{ top: 8, bottom: 8, left: 8, right: 8 }}
|
|
282
277
|
>
|
|
283
|
-
<Text
|
|
284
|
-
style={{
|
|
285
|
-
fontSize: 18,
|
|
286
|
-
fontWeight: '600',
|
|
287
|
-
color: colors.icon,
|
|
288
|
-
}}
|
|
289
|
-
>
|
|
278
|
+
<Text className="text-lg font-semibold text-muted-foreground">
|
|
290
279
|
{'\u2715'}
|
|
291
280
|
</Text>
|
|
292
281
|
</TouchableOpacity>
|
|
@@ -318,7 +307,7 @@ AliaChatSheet.displayName = 'AliaChatSheet';
|
|
|
318
307
|
|
|
319
308
|
const styles = StyleSheet.create({
|
|
320
309
|
backdrop: {
|
|
321
|
-
...StyleSheet.
|
|
310
|
+
...StyleSheet.absoluteFill,
|
|
322
311
|
backgroundColor: 'rgba(0,0,0,0.5)',
|
|
323
312
|
},
|
|
324
313
|
sheet: {
|
|
@@ -361,10 +350,6 @@ const styles = StyleSheet.create({
|
|
|
361
350
|
alignItems: 'center',
|
|
362
351
|
gap: 10,
|
|
363
352
|
},
|
|
364
|
-
headerTitle: {
|
|
365
|
-
fontSize: 18,
|
|
366
|
-
fontWeight: '600',
|
|
367
|
-
},
|
|
368
353
|
headerRight: {
|
|
369
354
|
flexDirection: 'row',
|
|
370
355
|
alignItems: 'center',
|
|
@@ -374,9 +359,6 @@ const styles = StyleSheet.create({
|
|
|
374
359
|
paddingHorizontal: 10,
|
|
375
360
|
paddingVertical: 6,
|
|
376
361
|
},
|
|
377
|
-
clearText: {
|
|
378
|
-
fontSize: 14,
|
|
379
|
-
},
|
|
380
362
|
closeButton: {
|
|
381
363
|
width: 32,
|
|
382
364
|
height: 32,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React, { useEffect } from "react";
|
|
2
|
-
import { View, Text } from "react-native";
|
|
1
|
+
import React, { useEffect, useMemo } from "react";
|
|
2
|
+
import { View, Text, Image, ImageSourcePropType } from "react-native";
|
|
3
3
|
import Svg, { Path, Ellipse } from "react-native-svg";
|
|
4
4
|
import Animated, {
|
|
5
5
|
useSharedValue,
|
|
@@ -12,7 +12,6 @@ import Animated, {
|
|
|
12
12
|
cancelAnimation,
|
|
13
13
|
Easing,
|
|
14
14
|
} from "react-native-reanimated";
|
|
15
|
-
import { useAliaColors } from "../theme";
|
|
16
15
|
|
|
17
16
|
// ─── Animated SVG wrappers ──────────────────────────────────────────────────
|
|
18
17
|
|
|
@@ -33,10 +32,19 @@ export type AliaExpression =
|
|
|
33
32
|
| "Searching F"
|
|
34
33
|
| "Looking Down";
|
|
35
34
|
|
|
35
|
+
export interface AliaAccessory {
|
|
36
|
+
layer: 'front' | 'behind';
|
|
37
|
+
image: ImageSourcePropType;
|
|
38
|
+
/** Optional position overrides (ratios 0-1, scale multiplier, rotation in degrees) */
|
|
39
|
+
position?: { x: number; y: number; scale: number; rotation: number };
|
|
40
|
+
}
|
|
41
|
+
|
|
36
42
|
export interface AliaFaceProps {
|
|
37
43
|
expression?: AliaExpression;
|
|
38
44
|
size?: number;
|
|
39
45
|
showLabel?: boolean;
|
|
46
|
+
/** Accessory overlays rendered on/around the face circle */
|
|
47
|
+
accessories?: AliaAccessory[];
|
|
40
48
|
}
|
|
41
49
|
|
|
42
50
|
// ─── Expression data (numeric decomposition of SVG paths) ───────────────────
|
|
@@ -129,11 +137,9 @@ export function AliaFace({
|
|
|
129
137
|
expression = DEFAULT_EXPRESSION,
|
|
130
138
|
size = 120,
|
|
131
139
|
showLabel = false,
|
|
140
|
+
accessories,
|
|
132
141
|
}: AliaFaceProps) {
|
|
133
|
-
const
|
|
134
|
-
const strokeColor = colors.isDark ? "#ffffff" : "#000000";
|
|
135
|
-
const circleBg = colors.background;
|
|
136
|
-
const circleBorder = colors.border;
|
|
142
|
+
const strokeColor = 'currentColor';
|
|
137
143
|
|
|
138
144
|
const initial = EXPRESSIONS[DEFAULT_EXPRESSION];
|
|
139
145
|
|
|
@@ -336,64 +342,108 @@ export function AliaFace({
|
|
|
336
342
|
transform: [{ translateY: breatheY.value }],
|
|
337
343
|
}));
|
|
338
344
|
|
|
345
|
+
// ── Accessories ──
|
|
346
|
+
|
|
347
|
+
const { behind, front } = useMemo(() => {
|
|
348
|
+
if (!accessories?.length) return { behind: [] as AliaAccessory[], front: [] as AliaAccessory[] };
|
|
349
|
+
const b: AliaAccessory[] = [];
|
|
350
|
+
const f: AliaAccessory[] = [];
|
|
351
|
+
for (const acc of accessories) {
|
|
352
|
+
if (acc.layer === 'behind') b.push(acc);
|
|
353
|
+
else f.push(acc);
|
|
354
|
+
}
|
|
355
|
+
return { behind: b, front: f };
|
|
356
|
+
}, [accessories]);
|
|
357
|
+
|
|
358
|
+
const accessoryStyle = useMemo(
|
|
359
|
+
() => ({ position: 'absolute' as const, width: size, height: size, top: 0, left: 0 }),
|
|
360
|
+
[size],
|
|
361
|
+
);
|
|
362
|
+
|
|
339
363
|
// ── Render ──
|
|
340
364
|
|
|
341
365
|
return (
|
|
342
366
|
<View style={{ alignItems: "center" }}>
|
|
343
|
-
<View
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
367
|
+
<View style={{ width: size, height: size, overflow: 'visible' as const }}>
|
|
368
|
+
{/* Behind-layer accessories (e.g. headphones) */}
|
|
369
|
+
{behind.map((acc, i) => (
|
|
370
|
+
<Image key={`behind-${i}`} source={acc.image} style={acc.position ? {
|
|
371
|
+
position: 'absolute',
|
|
372
|
+
width: size * acc.position.scale,
|
|
373
|
+
height: size * acc.position.scale,
|
|
374
|
+
top: (acc.position.y - 0.5) * size + (size - size * acc.position.scale) / 2,
|
|
375
|
+
left: (acc.position.x - 0.5) * size + (size - size * acc.position.scale) / 2,
|
|
376
|
+
transform: [{ rotate: `${acc.position.rotation}deg` }],
|
|
377
|
+
} : accessoryStyle} />
|
|
378
|
+
))}
|
|
379
|
+
|
|
380
|
+
{/* Face circle */}
|
|
381
|
+
<View
|
|
382
|
+
className="bg-background border-primary/20 text-foreground"
|
|
383
|
+
style={{
|
|
384
|
+
width: size,
|
|
385
|
+
height: size,
|
|
386
|
+
borderRadius: size / 2,
|
|
387
|
+
alignItems: "center",
|
|
388
|
+
justifyContent: "center",
|
|
389
|
+
borderWidth: 1,
|
|
390
|
+
}}
|
|
391
|
+
>
|
|
392
|
+
<Animated.View style={breatheStyle}>
|
|
393
|
+
<Svg
|
|
394
|
+
width={size * 0.95}
|
|
395
|
+
height={size * 0.95}
|
|
396
|
+
viewBox="35 35 250 250"
|
|
397
|
+
>
|
|
398
|
+
{/* Left brow */}
|
|
399
|
+
<AnimatedPath
|
|
400
|
+
animatedProps={leftBrowProps}
|
|
401
|
+
stroke={strokeColor}
|
|
402
|
+
strokeWidth={15}
|
|
403
|
+
strokeLinecap="round"
|
|
404
|
+
fill="none"
|
|
405
|
+
/>
|
|
406
|
+
|
|
407
|
+
{/* Left eye */}
|
|
408
|
+
<AnimatedEllipse
|
|
409
|
+
animatedProps={leftEyeProps}
|
|
410
|
+
fill={strokeColor}
|
|
411
|
+
/>
|
|
412
|
+
|
|
413
|
+
{/* Right eye */}
|
|
414
|
+
<AnimatedEllipse
|
|
415
|
+
animatedProps={rightEyeProps}
|
|
416
|
+
fill={strokeColor}
|
|
417
|
+
/>
|
|
418
|
+
|
|
419
|
+
{/* Nose + right brow */}
|
|
420
|
+
<AnimatedPath
|
|
421
|
+
animatedProps={noseBrowProps}
|
|
422
|
+
stroke={strokeColor}
|
|
423
|
+
strokeWidth={15}
|
|
424
|
+
strokeLinecap="round"
|
|
425
|
+
strokeLinejoin="round"
|
|
426
|
+
fill="none"
|
|
427
|
+
/>
|
|
428
|
+
</Svg>
|
|
429
|
+
</Animated.View>
|
|
430
|
+
</View>
|
|
431
|
+
|
|
432
|
+
{/* Front-layer accessories (e.g. hat, glasses, tie) */}
|
|
433
|
+
{front.map((acc, i) => (
|
|
434
|
+
<Image key={`front-${i}`} source={acc.image} style={acc.position ? {
|
|
435
|
+
position: 'absolute',
|
|
436
|
+
width: size * acc.position.scale,
|
|
437
|
+
height: size * acc.position.scale,
|
|
438
|
+
top: (acc.position.y - 0.5) * size + (size - size * acc.position.scale) / 2,
|
|
439
|
+
left: (acc.position.x - 0.5) * size + (size - size * acc.position.scale) / 2,
|
|
440
|
+
transform: [{ rotate: `${acc.position.rotation}deg` }],
|
|
441
|
+
} : accessoryStyle} />
|
|
442
|
+
))}
|
|
393
443
|
</View>
|
|
394
444
|
|
|
395
445
|
{showLabel && (
|
|
396
|
-
<Text style={{ fontSize: 10,
|
|
446
|
+
<Text className="text-muted-foreground" style={{ fontSize: 10, marginTop: 8, textTransform: 'uppercase', letterSpacing: 3, fontWeight: '700' }}>
|
|
397
447
|
{expression}
|
|
398
448
|
</Text>
|
|
399
449
|
)}
|