@metacells/mcellui-mcp-server 0.1.1 → 0.1.3
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/dist/index.js +14 -3
- package/package.json +5 -3
- package/registry/registry.json +717 -0
- package/registry/ui/accordion.tsx +416 -0
- package/registry/ui/action-sheet.tsx +396 -0
- package/registry/ui/alert-dialog.tsx +355 -0
- package/registry/ui/avatar-stack.tsx +278 -0
- package/registry/ui/avatar.tsx +116 -0
- package/registry/ui/badge.tsx +125 -0
- package/registry/ui/button.tsx +240 -0
- package/registry/ui/card.tsx +675 -0
- package/registry/ui/carousel.tsx +431 -0
- package/registry/ui/checkbox.tsx +252 -0
- package/registry/ui/chip.tsx +271 -0
- package/registry/ui/column.tsx +133 -0
- package/registry/ui/datetime-picker.tsx +578 -0
- package/registry/ui/dialog.tsx +292 -0
- package/registry/ui/fab.tsx +225 -0
- package/registry/ui/form.tsx +323 -0
- package/registry/ui/horizontal-list.tsx +200 -0
- package/registry/ui/icon-button.tsx +244 -0
- package/registry/ui/image-gallery.tsx +455 -0
- package/registry/ui/image.tsx +283 -0
- package/registry/ui/input.tsx +242 -0
- package/registry/ui/label.tsx +99 -0
- package/registry/ui/list.tsx +519 -0
- package/registry/ui/progress.tsx +168 -0
- package/registry/ui/pull-to-refresh.tsx +231 -0
- package/registry/ui/radio-group.tsx +294 -0
- package/registry/ui/rating.tsx +311 -0
- package/registry/ui/row.tsx +136 -0
- package/registry/ui/screen.tsx +153 -0
- package/registry/ui/search-input.tsx +281 -0
- package/registry/ui/section-header.tsx +258 -0
- package/registry/ui/segmented-control.tsx +229 -0
- package/registry/ui/select.tsx +311 -0
- package/registry/ui/separator.tsx +74 -0
- package/registry/ui/sheet.tsx +362 -0
- package/registry/ui/skeleton.tsx +156 -0
- package/registry/ui/slider.tsx +307 -0
- package/registry/ui/spinner.tsx +100 -0
- package/registry/ui/stepper.tsx +314 -0
- package/registry/ui/stories.tsx +463 -0
- package/registry/ui/swipeable-row.tsx +362 -0
- package/registry/ui/switch.tsx +246 -0
- package/registry/ui/tabs.tsx +348 -0
- package/registry/ui/textarea.tsx +265 -0
- package/registry/ui/toast.tsx +316 -0
- package/registry/ui/tooltip.tsx +369 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Dialog
|
|
3
|
+
*
|
|
4
|
+
* A modal dialog component with backdrop and animations.
|
|
5
|
+
* Centers content on screen with fade/scale animation.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const [open, setOpen] = useState(false);
|
|
10
|
+
*
|
|
11
|
+
* <Dialog open={open} onOpenChange={setOpen}>
|
|
12
|
+
* <DialogContent>
|
|
13
|
+
* <DialogHeader>
|
|
14
|
+
* <DialogTitle>Are you sure?</DialogTitle>
|
|
15
|
+
* <DialogDescription>This action cannot be undone.</DialogDescription>
|
|
16
|
+
* </DialogHeader>
|
|
17
|
+
* <DialogFooter>
|
|
18
|
+
* <DialogClose asChild>
|
|
19
|
+
* <Button variant="outline">Cancel</Button>
|
|
20
|
+
* </DialogClose>
|
|
21
|
+
* <Button>Continue</Button>
|
|
22
|
+
* </DialogFooter>
|
|
23
|
+
* </DialogContent>
|
|
24
|
+
* </Dialog>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
import React, { useEffect, useCallback, createContext, useContext } from 'react';
|
|
29
|
+
import {
|
|
30
|
+
View,
|
|
31
|
+
Text,
|
|
32
|
+
Modal,
|
|
33
|
+
Pressable,
|
|
34
|
+
StyleSheet,
|
|
35
|
+
ViewStyle,
|
|
36
|
+
TextStyle,
|
|
37
|
+
Dimensions,
|
|
38
|
+
KeyboardAvoidingView,
|
|
39
|
+
Platform,
|
|
40
|
+
} from 'react-native';
|
|
41
|
+
import Animated, {
|
|
42
|
+
useSharedValue,
|
|
43
|
+
useAnimatedStyle,
|
|
44
|
+
withSpring,
|
|
45
|
+
withTiming,
|
|
46
|
+
runOnJS,
|
|
47
|
+
interpolate,
|
|
48
|
+
} from 'react-native-reanimated';
|
|
49
|
+
import { useTheme, DIALOG_CONSTANTS } from '@nativeui/core';
|
|
50
|
+
|
|
51
|
+
const { width: SCREEN_WIDTH } = Dimensions.get('window');
|
|
52
|
+
|
|
53
|
+
// Context for dialog state
|
|
54
|
+
const DialogContext = createContext<{
|
|
55
|
+
onClose: () => void;
|
|
56
|
+
} | null>(null);
|
|
57
|
+
|
|
58
|
+
const useDialog = () => {
|
|
59
|
+
const context = useContext(DialogContext);
|
|
60
|
+
if (!context) {
|
|
61
|
+
throw new Error('Dialog components must be used within a Dialog');
|
|
62
|
+
}
|
|
63
|
+
return context;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
export interface DialogProps {
|
|
67
|
+
/** Controlled open state */
|
|
68
|
+
open: boolean;
|
|
69
|
+
/** Callback when open state changes */
|
|
70
|
+
onOpenChange: (open: boolean) => void;
|
|
71
|
+
/** Children (should be DialogContent) */
|
|
72
|
+
children: React.ReactNode;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function Dialog({ open, onOpenChange, children }: DialogProps) {
|
|
76
|
+
const handleClose = useCallback(() => {
|
|
77
|
+
onOpenChange(false);
|
|
78
|
+
}, [onOpenChange]);
|
|
79
|
+
|
|
80
|
+
return (
|
|
81
|
+
<Modal
|
|
82
|
+
visible={open}
|
|
83
|
+
transparent
|
|
84
|
+
animationType="none"
|
|
85
|
+
statusBarTranslucent
|
|
86
|
+
onRequestClose={handleClose}
|
|
87
|
+
>
|
|
88
|
+
<DialogContext.Provider value={{ onClose: handleClose }}>
|
|
89
|
+
{children}
|
|
90
|
+
</DialogContext.Provider>
|
|
91
|
+
</Modal>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface DialogContentProps {
|
|
96
|
+
/** Content children */
|
|
97
|
+
children: React.ReactNode;
|
|
98
|
+
/** Max width of dialog */
|
|
99
|
+
maxWidth?: number;
|
|
100
|
+
/** Additional styles */
|
|
101
|
+
style?: ViewStyle;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function DialogContent({
|
|
105
|
+
children,
|
|
106
|
+
maxWidth = SCREEN_WIDTH - DIALOG_CONSTANTS.screenMargin,
|
|
107
|
+
style,
|
|
108
|
+
}: DialogContentProps) {
|
|
109
|
+
const { colors, radius, platformShadow, springs } = useTheme();
|
|
110
|
+
const { onClose } = useDialog();
|
|
111
|
+
|
|
112
|
+
const progress = useSharedValue(0);
|
|
113
|
+
const isClosing = useSharedValue(false);
|
|
114
|
+
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
progress.value = withSpring(1, springs.snappy);
|
|
117
|
+
}, []);
|
|
118
|
+
|
|
119
|
+
const closeDialog = useCallback(() => {
|
|
120
|
+
if (isClosing.value) return;
|
|
121
|
+
isClosing.value = true;
|
|
122
|
+
progress.value = withTiming(0, { duration: DIALOG_CONSTANTS.closeAnimationDuration }, (finished) => {
|
|
123
|
+
if (finished) {
|
|
124
|
+
runOnJS(onClose)();
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}, [onClose]);
|
|
128
|
+
|
|
129
|
+
const animatedBackdropStyle = useAnimatedStyle(() => ({
|
|
130
|
+
opacity: interpolate(progress.value, [0, 1], [0, DIALOG_CONSTANTS.backdropMaxOpacity]),
|
|
131
|
+
}));
|
|
132
|
+
|
|
133
|
+
const animatedDialogStyle = useAnimatedStyle(() => ({
|
|
134
|
+
opacity: progress.value,
|
|
135
|
+
transform: [
|
|
136
|
+
{ scale: interpolate(progress.value, [0, 1], [DIALOG_CONSTANTS.enterStartScale, 1]) },
|
|
137
|
+
],
|
|
138
|
+
}));
|
|
139
|
+
|
|
140
|
+
return (
|
|
141
|
+
<View style={styles.container}>
|
|
142
|
+
<Animated.View
|
|
143
|
+
style={[
|
|
144
|
+
styles.backdrop,
|
|
145
|
+
{ backgroundColor: colors.foreground },
|
|
146
|
+
animatedBackdropStyle,
|
|
147
|
+
]}
|
|
148
|
+
>
|
|
149
|
+
<Pressable style={styles.backdropPressable} onPress={closeDialog} />
|
|
150
|
+
</Animated.View>
|
|
151
|
+
|
|
152
|
+
<KeyboardAvoidingView
|
|
153
|
+
behavior={Platform.OS === 'ios' ? 'padding' : undefined}
|
|
154
|
+
style={styles.keyboardView}
|
|
155
|
+
>
|
|
156
|
+
<Animated.View
|
|
157
|
+
style={[
|
|
158
|
+
styles.dialog,
|
|
159
|
+
{
|
|
160
|
+
maxWidth,
|
|
161
|
+
backgroundColor: colors.card,
|
|
162
|
+
borderRadius: radius.xl,
|
|
163
|
+
},
|
|
164
|
+
platformShadow('lg'),
|
|
165
|
+
animatedDialogStyle,
|
|
166
|
+
style,
|
|
167
|
+
]}
|
|
168
|
+
>
|
|
169
|
+
{children}
|
|
170
|
+
</Animated.View>
|
|
171
|
+
</KeyboardAvoidingView>
|
|
172
|
+
</View>
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export interface DialogHeaderProps {
|
|
177
|
+
children: React.ReactNode;
|
|
178
|
+
style?: ViewStyle;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export function DialogHeader({ children, style }: DialogHeaderProps) {
|
|
182
|
+
const { spacing } = useTheme();
|
|
183
|
+
return (
|
|
184
|
+
<View style={[styles.header, { marginBottom: spacing[4] }, style]}>
|
|
185
|
+
{children}
|
|
186
|
+
</View>
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export interface DialogTitleProps {
|
|
191
|
+
children: React.ReactNode;
|
|
192
|
+
style?: TextStyle;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function DialogTitle({ children, style }: DialogTitleProps) {
|
|
196
|
+
const { colors } = useTheme();
|
|
197
|
+
return (
|
|
198
|
+
<Text style={[styles.title, { color: colors.foreground }, style]}>
|
|
199
|
+
{children}
|
|
200
|
+
</Text>
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
export interface DialogDescriptionProps {
|
|
205
|
+
children: React.ReactNode;
|
|
206
|
+
style?: TextStyle;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function DialogDescription({ children, style }: DialogDescriptionProps) {
|
|
210
|
+
const { colors, spacing } = useTheme();
|
|
211
|
+
return (
|
|
212
|
+
<Text
|
|
213
|
+
style={[
|
|
214
|
+
styles.description,
|
|
215
|
+
{ color: colors.foregroundMuted, marginTop: spacing[2] },
|
|
216
|
+
style,
|
|
217
|
+
]}
|
|
218
|
+
>
|
|
219
|
+
{children}
|
|
220
|
+
</Text>
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export interface DialogFooterProps {
|
|
225
|
+
children: React.ReactNode;
|
|
226
|
+
style?: ViewStyle;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export function DialogFooter({ children, style }: DialogFooterProps) {
|
|
230
|
+
const { spacing } = useTheme();
|
|
231
|
+
return (
|
|
232
|
+
<View style={[styles.footer, { marginTop: spacing[6], gap: spacing[3] }, style]}>
|
|
233
|
+
{children}
|
|
234
|
+
</View>
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export interface DialogCloseProps {
|
|
239
|
+
children: React.ReactNode;
|
|
240
|
+
asChild?: boolean;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
export function DialogClose({ children, asChild }: DialogCloseProps) {
|
|
244
|
+
const { onClose } = useDialog();
|
|
245
|
+
|
|
246
|
+
if (asChild && React.isValidElement(children)) {
|
|
247
|
+
return React.cloneElement(children as React.ReactElement<any>, {
|
|
248
|
+
onPress: onClose,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return (
|
|
253
|
+
<Pressable onPress={onClose}>
|
|
254
|
+
{children}
|
|
255
|
+
</Pressable>
|
|
256
|
+
);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const styles = StyleSheet.create({
|
|
260
|
+
container: {
|
|
261
|
+
flex: 1,
|
|
262
|
+
justifyContent: 'center',
|
|
263
|
+
alignItems: 'center',
|
|
264
|
+
},
|
|
265
|
+
backdrop: {
|
|
266
|
+
...StyleSheet.absoluteFillObject,
|
|
267
|
+
},
|
|
268
|
+
backdropPressable: {
|
|
269
|
+
flex: 1,
|
|
270
|
+
},
|
|
271
|
+
keyboardView: {
|
|
272
|
+
justifyContent: 'center',
|
|
273
|
+
alignItems: 'center',
|
|
274
|
+
},
|
|
275
|
+
dialog: {
|
|
276
|
+
width: '100%',
|
|
277
|
+
padding: DIALOG_CONSTANTS.contentPadding,
|
|
278
|
+
},
|
|
279
|
+
header: {},
|
|
280
|
+
title: {
|
|
281
|
+
fontSize: 18,
|
|
282
|
+
fontWeight: '600',
|
|
283
|
+
},
|
|
284
|
+
description: {
|
|
285
|
+
fontSize: 14,
|
|
286
|
+
lineHeight: 20,
|
|
287
|
+
},
|
|
288
|
+
footer: {
|
|
289
|
+
flexDirection: 'row',
|
|
290
|
+
justifyContent: 'flex-end',
|
|
291
|
+
},
|
|
292
|
+
});
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FAB (Floating Action Button)
|
|
3
|
+
*
|
|
4
|
+
* A prominent button for primary actions, typically positioned
|
|
5
|
+
* at the bottom-right of the screen.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```tsx
|
|
9
|
+
* // Basic FAB
|
|
10
|
+
* <FAB icon={<PlusIcon />} onPress={handleCreate} />
|
|
11
|
+
*
|
|
12
|
+
* // Extended FAB with label
|
|
13
|
+
* <FAB icon={<PlusIcon />} label="Create" onPress={handleCreate} />
|
|
14
|
+
*
|
|
15
|
+
* // Different sizes
|
|
16
|
+
* <FAB icon={<PlusIcon />} size="sm" />
|
|
17
|
+
* <FAB icon={<PlusIcon />} size="lg" />
|
|
18
|
+
*
|
|
19
|
+
* // With absolute positioning (typical usage)
|
|
20
|
+
* <View style={{ flex: 1 }}>
|
|
21
|
+
* <Content />
|
|
22
|
+
* <FAB
|
|
23
|
+
* icon={<PlusIcon />}
|
|
24
|
+
* onPress={handleCreate}
|
|
25
|
+
* style={{ position: 'absolute', bottom: 24, right: 24 }}
|
|
26
|
+
* />
|
|
27
|
+
* </View>
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import React from 'react';
|
|
32
|
+
import {
|
|
33
|
+
Text,
|
|
34
|
+
Pressable,
|
|
35
|
+
StyleSheet,
|
|
36
|
+
ViewStyle,
|
|
37
|
+
ActivityIndicator,
|
|
38
|
+
} from 'react-native';
|
|
39
|
+
import Animated, {
|
|
40
|
+
useAnimatedStyle,
|
|
41
|
+
useSharedValue,
|
|
42
|
+
withSpring,
|
|
43
|
+
} from 'react-native-reanimated';
|
|
44
|
+
import { useTheme, haptic } from '@nativeui/core';
|
|
45
|
+
|
|
46
|
+
const AnimatedPressable = Animated.createAnimatedComponent(Pressable);
|
|
47
|
+
|
|
48
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
49
|
+
// Types
|
|
50
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
export type FABSize = 'sm' | 'md' | 'lg';
|
|
53
|
+
export type FABVariant = 'primary' | 'secondary' | 'surface';
|
|
54
|
+
|
|
55
|
+
export interface FABProps {
|
|
56
|
+
/** Icon element */
|
|
57
|
+
icon: React.ReactNode;
|
|
58
|
+
/** Optional label for extended FAB */
|
|
59
|
+
label?: string;
|
|
60
|
+
/** Size preset */
|
|
61
|
+
size?: FABSize;
|
|
62
|
+
/** Visual variant */
|
|
63
|
+
variant?: FABVariant;
|
|
64
|
+
/** Loading state */
|
|
65
|
+
loading?: boolean;
|
|
66
|
+
/** Disabled state */
|
|
67
|
+
disabled?: boolean;
|
|
68
|
+
/** Press handler */
|
|
69
|
+
onPress?: () => void;
|
|
70
|
+
/** Container style */
|
|
71
|
+
style?: ViewStyle;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
75
|
+
// Size configs
|
|
76
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
const SIZE_CONFIG = {
|
|
79
|
+
sm: {
|
|
80
|
+
size: 40,
|
|
81
|
+
iconSize: 18,
|
|
82
|
+
fontSize: 13,
|
|
83
|
+
paddingHorizontal: 12,
|
|
84
|
+
},
|
|
85
|
+
md: {
|
|
86
|
+
size: 56,
|
|
87
|
+
iconSize: 24,
|
|
88
|
+
fontSize: 14,
|
|
89
|
+
paddingHorizontal: 16,
|
|
90
|
+
},
|
|
91
|
+
lg: {
|
|
92
|
+
size: 72,
|
|
93
|
+
iconSize: 32,
|
|
94
|
+
fontSize: 16,
|
|
95
|
+
paddingHorizontal: 20,
|
|
96
|
+
},
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
100
|
+
// Component
|
|
101
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
export function FAB({
|
|
104
|
+
icon,
|
|
105
|
+
label,
|
|
106
|
+
size = 'md',
|
|
107
|
+
variant = 'primary',
|
|
108
|
+
loading = false,
|
|
109
|
+
disabled = false,
|
|
110
|
+
onPress,
|
|
111
|
+
style,
|
|
112
|
+
}: FABProps) {
|
|
113
|
+
const { colors, fontWeight, platformShadow } = useTheme();
|
|
114
|
+
const config = SIZE_CONFIG[size];
|
|
115
|
+
|
|
116
|
+
const scale = useSharedValue(1);
|
|
117
|
+
|
|
118
|
+
// Variant styles
|
|
119
|
+
const variantStyles = {
|
|
120
|
+
primary: {
|
|
121
|
+
backgroundColor: colors.primary,
|
|
122
|
+
iconColor: colors.primaryForeground,
|
|
123
|
+
},
|
|
124
|
+
secondary: {
|
|
125
|
+
backgroundColor: colors.secondary,
|
|
126
|
+
iconColor: colors.secondaryForeground,
|
|
127
|
+
},
|
|
128
|
+
surface: {
|
|
129
|
+
backgroundColor: colors.card,
|
|
130
|
+
iconColor: colors.foreground,
|
|
131
|
+
},
|
|
132
|
+
}[variant];
|
|
133
|
+
|
|
134
|
+
const animatedStyle = useAnimatedStyle(() => ({
|
|
135
|
+
transform: [{ scale: scale.value }],
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
const handlePressIn = () => {
|
|
139
|
+
scale.value = withSpring(0.92, { damping: 20, stiffness: 400 });
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const handlePressOut = () => {
|
|
143
|
+
scale.value = withSpring(1, { damping: 20, stiffness: 400 });
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const handlePress = () => {
|
|
147
|
+
haptic('light');
|
|
148
|
+
onPress?.();
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
const isExtended = !!label;
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<AnimatedPressable
|
|
155
|
+
onPress={handlePress}
|
|
156
|
+
onPressIn={handlePressIn}
|
|
157
|
+
onPressOut={handlePressOut}
|
|
158
|
+
disabled={disabled || loading}
|
|
159
|
+
style={[
|
|
160
|
+
styles.fab,
|
|
161
|
+
{
|
|
162
|
+
backgroundColor: variantStyles.backgroundColor,
|
|
163
|
+
width: isExtended ? 'auto' : config.size,
|
|
164
|
+
height: config.size,
|
|
165
|
+
borderRadius: config.size / 2,
|
|
166
|
+
paddingHorizontal: isExtended ? config.paddingHorizontal : 0,
|
|
167
|
+
opacity: disabled ? 0.5 : 1,
|
|
168
|
+
},
|
|
169
|
+
platformShadow('lg'),
|
|
170
|
+
animatedStyle,
|
|
171
|
+
style,
|
|
172
|
+
]}
|
|
173
|
+
accessibilityRole="button"
|
|
174
|
+
accessibilityLabel={label || 'Floating action button'}
|
|
175
|
+
accessibilityState={{ disabled }}
|
|
176
|
+
>
|
|
177
|
+
{loading ? (
|
|
178
|
+
<ActivityIndicator
|
|
179
|
+
size={size === 'sm' ? 'small' : 'small'}
|
|
180
|
+
color={variantStyles.iconColor}
|
|
181
|
+
/>
|
|
182
|
+
) : (
|
|
183
|
+
<>
|
|
184
|
+
{React.isValidElement(icon)
|
|
185
|
+
? React.cloneElement(icon as React.ReactElement<{ width?: number; height?: number; color?: string }>, {
|
|
186
|
+
width: config.iconSize,
|
|
187
|
+
height: config.iconSize,
|
|
188
|
+
color: variantStyles.iconColor,
|
|
189
|
+
})
|
|
190
|
+
: icon}
|
|
191
|
+
{label && (
|
|
192
|
+
<Text
|
|
193
|
+
style={[
|
|
194
|
+
styles.label,
|
|
195
|
+
{
|
|
196
|
+
fontSize: config.fontSize,
|
|
197
|
+
fontWeight: fontWeight.semibold,
|
|
198
|
+
color: variantStyles.iconColor,
|
|
199
|
+
marginLeft: 8,
|
|
200
|
+
},
|
|
201
|
+
]}
|
|
202
|
+
>
|
|
203
|
+
{label}
|
|
204
|
+
</Text>
|
|
205
|
+
)}
|
|
206
|
+
</>
|
|
207
|
+
)}
|
|
208
|
+
</AnimatedPressable>
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
213
|
+
// Styles
|
|
214
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
215
|
+
|
|
216
|
+
const styles = StyleSheet.create({
|
|
217
|
+
fab: {
|
|
218
|
+
flexDirection: 'row',
|
|
219
|
+
alignItems: 'center',
|
|
220
|
+
justifyContent: 'center',
|
|
221
|
+
},
|
|
222
|
+
label: {
|
|
223
|
+
textAlign: 'center',
|
|
224
|
+
},
|
|
225
|
+
});
|