@metacells/mcellui-mcp-server 0.1.1 → 0.1.2

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/dist/index.js +8 -2
  2. package/package.json +5 -3
  3. package/registry/registry.json +717 -0
  4. package/registry/ui/accordion.tsx +416 -0
  5. package/registry/ui/action-sheet.tsx +396 -0
  6. package/registry/ui/alert-dialog.tsx +355 -0
  7. package/registry/ui/avatar-stack.tsx +278 -0
  8. package/registry/ui/avatar.tsx +116 -0
  9. package/registry/ui/badge.tsx +125 -0
  10. package/registry/ui/button.tsx +240 -0
  11. package/registry/ui/card.tsx +675 -0
  12. package/registry/ui/carousel.tsx +431 -0
  13. package/registry/ui/checkbox.tsx +252 -0
  14. package/registry/ui/chip.tsx +271 -0
  15. package/registry/ui/column.tsx +133 -0
  16. package/registry/ui/datetime-picker.tsx +578 -0
  17. package/registry/ui/dialog.tsx +292 -0
  18. package/registry/ui/fab.tsx +225 -0
  19. package/registry/ui/form.tsx +323 -0
  20. package/registry/ui/horizontal-list.tsx +200 -0
  21. package/registry/ui/icon-button.tsx +244 -0
  22. package/registry/ui/image-gallery.tsx +455 -0
  23. package/registry/ui/image.tsx +283 -0
  24. package/registry/ui/input.tsx +242 -0
  25. package/registry/ui/label.tsx +99 -0
  26. package/registry/ui/list.tsx +519 -0
  27. package/registry/ui/progress.tsx +168 -0
  28. package/registry/ui/pull-to-refresh.tsx +231 -0
  29. package/registry/ui/radio-group.tsx +294 -0
  30. package/registry/ui/rating.tsx +311 -0
  31. package/registry/ui/row.tsx +136 -0
  32. package/registry/ui/screen.tsx +153 -0
  33. package/registry/ui/search-input.tsx +281 -0
  34. package/registry/ui/section-header.tsx +258 -0
  35. package/registry/ui/segmented-control.tsx +229 -0
  36. package/registry/ui/select.tsx +311 -0
  37. package/registry/ui/separator.tsx +74 -0
  38. package/registry/ui/sheet.tsx +362 -0
  39. package/registry/ui/skeleton.tsx +156 -0
  40. package/registry/ui/slider.tsx +307 -0
  41. package/registry/ui/spinner.tsx +100 -0
  42. package/registry/ui/stepper.tsx +314 -0
  43. package/registry/ui/stories.tsx +463 -0
  44. package/registry/ui/swipeable-row.tsx +362 -0
  45. package/registry/ui/switch.tsx +246 -0
  46. package/registry/ui/tabs.tsx +348 -0
  47. package/registry/ui/textarea.tsx +265 -0
  48. package/registry/ui/toast.tsx +316 -0
  49. 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
+ });