@metacells/mcellui-mcp-server 0.1.0 → 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 +70 -7
  2. package/package.json +7 -5
  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,355 @@
1
+ /**
2
+ * Alert Dialog
3
+ *
4
+ * A confirmation dialog that requires explicit user action.
5
+ * Cannot be dismissed by clicking backdrop (use for destructive actions).
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * const [open, setOpen] = useState(false);
10
+ *
11
+ * <AlertDialog open={open} onOpenChange={setOpen}>
12
+ * <AlertDialogContent>
13
+ * <AlertDialogHeader>
14
+ * <AlertDialogTitle>Delete Account</AlertDialogTitle>
15
+ * <AlertDialogDescription>
16
+ * This will permanently delete your account. This action cannot be undone.
17
+ * </AlertDialogDescription>
18
+ * </AlertDialogHeader>
19
+ * <AlertDialogFooter>
20
+ * <AlertDialogCancel>Cancel</AlertDialogCancel>
21
+ * <AlertDialogAction onPress={handleDelete}>Delete</AlertDialogAction>
22
+ * </AlertDialogFooter>
23
+ * </AlertDialogContent>
24
+ * </AlertDialog>
25
+ * ```
26
+ */
27
+
28
+ import React, { useEffect, useCallback, createContext, useContext } from 'react';
29
+ import {
30
+ View,
31
+ Text,
32
+ Modal,
33
+ StyleSheet,
34
+ ViewStyle,
35
+ TextStyle,
36
+ Dimensions,
37
+ Pressable,
38
+ PressableProps,
39
+ } from 'react-native';
40
+ import Animated, {
41
+ useSharedValue,
42
+ useAnimatedStyle,
43
+ withSpring,
44
+ withTiming,
45
+ runOnJS,
46
+ interpolate,
47
+ } from 'react-native-reanimated';
48
+ import { useTheme } from '@nativeui/core';
49
+ import { haptic } from '@nativeui/core';
50
+
51
+ const { width: SCREEN_WIDTH } = Dimensions.get('window');
52
+
53
+ // Context for alert dialog state
54
+ const AlertDialogContext = createContext<{
55
+ onClose: () => void;
56
+ } | null>(null);
57
+
58
+ const useAlertDialog = () => {
59
+ const context = useContext(AlertDialogContext);
60
+ if (!context) {
61
+ throw new Error('AlertDialog components must be used within an AlertDialog');
62
+ }
63
+ return context;
64
+ };
65
+
66
+ export interface AlertDialogProps {
67
+ /** Controlled open state */
68
+ open: boolean;
69
+ /** Callback when open state changes */
70
+ onOpenChange: (open: boolean) => void;
71
+ /** Children (should be AlertDialogContent) */
72
+ children: React.ReactNode;
73
+ }
74
+
75
+ export function AlertDialog({ open, onOpenChange, children }: AlertDialogProps) {
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
+ <AlertDialogContext.Provider value={{ onClose: handleClose }}>
89
+ {children}
90
+ </AlertDialogContext.Provider>
91
+ </Modal>
92
+ );
93
+ }
94
+
95
+ export interface AlertDialogContentProps {
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 AlertDialogContent({
105
+ children,
106
+ maxWidth = SCREEN_WIDTH - 48,
107
+ style,
108
+ }: AlertDialogContentProps) {
109
+ const { colors, radius, platformShadow, springs } = useTheme();
110
+
111
+ const progress = useSharedValue(0);
112
+
113
+ useEffect(() => {
114
+ progress.value = withSpring(1, springs.snappy);
115
+ }, []);
116
+
117
+ const animatedBackdropStyle = useAnimatedStyle(() => ({
118
+ opacity: interpolate(progress.value, [0, 1], [0, 0.5]),
119
+ }));
120
+
121
+ const animatedDialogStyle = useAnimatedStyle(() => ({
122
+ opacity: progress.value,
123
+ transform: [
124
+ { scale: interpolate(progress.value, [0, 1], [0.95, 1]) },
125
+ ],
126
+ }));
127
+
128
+ return (
129
+ <View style={styles.container}>
130
+ <Animated.View
131
+ style={[
132
+ styles.backdrop,
133
+ { backgroundColor: colors.foreground },
134
+ animatedBackdropStyle,
135
+ ]}
136
+ />
137
+
138
+ <Animated.View
139
+ style={[
140
+ styles.dialog,
141
+ {
142
+ maxWidth,
143
+ backgroundColor: colors.card,
144
+ borderRadius: radius.xl,
145
+ },
146
+ platformShadow('lg'),
147
+ animatedDialogStyle,
148
+ style,
149
+ ]}
150
+ >
151
+ {children}
152
+ </Animated.View>
153
+ </View>
154
+ );
155
+ }
156
+
157
+ export interface AlertDialogHeaderProps {
158
+ children: React.ReactNode;
159
+ style?: ViewStyle;
160
+ }
161
+
162
+ export function AlertDialogHeader({ children, style }: AlertDialogHeaderProps) {
163
+ const { spacing } = useTheme();
164
+ return (
165
+ <View style={[styles.header, { marginBottom: spacing[4] }, style]}>
166
+ {children}
167
+ </View>
168
+ );
169
+ }
170
+
171
+ export interface AlertDialogTitleProps {
172
+ children: React.ReactNode;
173
+ style?: TextStyle;
174
+ }
175
+
176
+ export function AlertDialogTitle({ children, style }: AlertDialogTitleProps) {
177
+ const { colors } = useTheme();
178
+ return (
179
+ <Text style={[styles.title, { color: colors.foreground }, style]}>
180
+ {children}
181
+ </Text>
182
+ );
183
+ }
184
+
185
+ export interface AlertDialogDescriptionProps {
186
+ children: React.ReactNode;
187
+ style?: TextStyle;
188
+ }
189
+
190
+ export function AlertDialogDescription({ children, style }: AlertDialogDescriptionProps) {
191
+ const { colors, spacing } = useTheme();
192
+ return (
193
+ <Text
194
+ style={[
195
+ styles.description,
196
+ { color: colors.foregroundMuted, marginTop: spacing[2] },
197
+ style,
198
+ ]}
199
+ >
200
+ {children}
201
+ </Text>
202
+ );
203
+ }
204
+
205
+ export interface AlertDialogFooterProps {
206
+ children: React.ReactNode;
207
+ style?: ViewStyle;
208
+ }
209
+
210
+ export function AlertDialogFooter({ children, style }: AlertDialogFooterProps) {
211
+ const { spacing } = useTheme();
212
+ return (
213
+ <View style={[styles.footer, { marginTop: spacing[6], gap: spacing[3] }, style]}>
214
+ {children}
215
+ </View>
216
+ );
217
+ }
218
+
219
+ export interface AlertDialogCancelProps extends Omit<PressableProps, 'style'> {
220
+ children: React.ReactNode;
221
+ style?: ViewStyle;
222
+ textStyle?: TextStyle;
223
+ }
224
+
225
+ export function AlertDialogCancel({
226
+ children,
227
+ style,
228
+ textStyle,
229
+ onPress,
230
+ ...props
231
+ }: AlertDialogCancelProps) {
232
+ const { colors, radius, spacing } = useTheme();
233
+ const { onClose } = useAlertDialog();
234
+
235
+ const handlePress = useCallback(
236
+ (e: any) => {
237
+ haptic('light');
238
+ onPress?.(e);
239
+ onClose();
240
+ },
241
+ [onPress, onClose]
242
+ );
243
+
244
+ return (
245
+ <Pressable
246
+ style={({ pressed }) => [
247
+ styles.button,
248
+ {
249
+ backgroundColor: colors.secondary,
250
+ borderRadius: radius.md,
251
+ paddingHorizontal: spacing[4],
252
+ paddingVertical: spacing[2.5],
253
+ opacity: pressed ? 0.7 : 1,
254
+ },
255
+ style,
256
+ ]}
257
+ onPress={handlePress}
258
+ {...props}
259
+ >
260
+ <Text style={[styles.buttonText, { color: colors.secondaryForeground }, textStyle]}>
261
+ {children}
262
+ </Text>
263
+ </Pressable>
264
+ );
265
+ }
266
+
267
+ export interface AlertDialogActionProps extends Omit<PressableProps, 'style'> {
268
+ children: React.ReactNode;
269
+ /** Use destructive styling */
270
+ destructive?: boolean;
271
+ style?: ViewStyle;
272
+ textStyle?: TextStyle;
273
+ }
274
+
275
+ export function AlertDialogAction({
276
+ children,
277
+ destructive = false,
278
+ style,
279
+ textStyle,
280
+ onPress,
281
+ ...props
282
+ }: AlertDialogActionProps) {
283
+ const { colors, radius, spacing } = useTheme();
284
+ const { onClose } = useAlertDialog();
285
+
286
+ const handlePress = useCallback(
287
+ (e: any) => {
288
+ haptic(destructive ? 'warning' : 'light');
289
+ onPress?.(e);
290
+ onClose();
291
+ },
292
+ [onPress, onClose, destructive]
293
+ );
294
+
295
+ const backgroundColor = destructive ? colors.destructive : colors.primary;
296
+ const textColor = destructive ? colors.destructiveForeground : colors.primaryForeground;
297
+
298
+ return (
299
+ <Pressable
300
+ style={({ pressed }) => [
301
+ styles.button,
302
+ {
303
+ backgroundColor,
304
+ borderRadius: radius.md,
305
+ paddingHorizontal: spacing[4],
306
+ paddingVertical: spacing[2.5],
307
+ opacity: pressed ? 0.7 : 1,
308
+ },
309
+ style,
310
+ ]}
311
+ onPress={handlePress}
312
+ {...props}
313
+ >
314
+ <Text style={[styles.buttonText, { color: textColor }, textStyle]}>
315
+ {children}
316
+ </Text>
317
+ </Pressable>
318
+ );
319
+ }
320
+
321
+ const styles = StyleSheet.create({
322
+ container: {
323
+ flex: 1,
324
+ justifyContent: 'center',
325
+ alignItems: 'center',
326
+ },
327
+ backdrop: {
328
+ ...StyleSheet.absoluteFillObject,
329
+ },
330
+ dialog: {
331
+ width: '100%',
332
+ padding: 24,
333
+ },
334
+ header: {},
335
+ title: {
336
+ fontSize: 18,
337
+ fontWeight: '600',
338
+ },
339
+ description: {
340
+ fontSize: 14,
341
+ lineHeight: 20,
342
+ },
343
+ footer: {
344
+ flexDirection: 'row',
345
+ justifyContent: 'flex-end',
346
+ },
347
+ button: {
348
+ alignItems: 'center',
349
+ justifyContent: 'center',
350
+ },
351
+ buttonText: {
352
+ fontSize: 14,
353
+ fontWeight: '600',
354
+ },
355
+ });
@@ -0,0 +1,278 @@
1
+ /**
2
+ * AvatarStack
3
+ *
4
+ * Overlapping avatar group for displaying multiple users.
5
+ * Shows a configurable number of avatars with an overflow indicator.
6
+ *
7
+ * @example
8
+ * ```tsx
9
+ * // Basic usage
10
+ * <AvatarStack
11
+ * avatars={[
12
+ * { source: { uri: 'https://...' }, name: 'John' },
13
+ * { source: { uri: 'https://...' }, name: 'Jane' },
14
+ * { source: { uri: 'https://...' }, name: 'Bob' },
15
+ * ]}
16
+ * />
17
+ *
18
+ * // With max count
19
+ * <AvatarStack
20
+ * avatars={users.map(u => ({ source: { uri: u.avatar }, name: u.name }))}
21
+ * max={3}
22
+ * size="lg"
23
+ * />
24
+ *
25
+ * // Different sizes
26
+ * <AvatarStack avatars={avatars} size="sm" />
27
+ * <AvatarStack avatars={avatars} size="md" />
28
+ * <AvatarStack avatars={avatars} size="lg" />
29
+ * ```
30
+ */
31
+
32
+ import React from 'react';
33
+ import {
34
+ View,
35
+ Text,
36
+ Image,
37
+ StyleSheet,
38
+ ViewStyle,
39
+ ImageSourcePropType,
40
+ } from 'react-native';
41
+ import { useTheme } from '@nativeui/core';
42
+
43
+ // ─────────────────────────────────────────────────────────────────────────────
44
+ // Types
45
+ // ─────────────────────────────────────────────────────────────────────────────
46
+
47
+ export type AvatarStackSize = 'sm' | 'md' | 'lg' | 'xl';
48
+
49
+ export interface AvatarItem {
50
+ /** Image source */
51
+ source?: ImageSourcePropType;
52
+ /** User name (used for fallback initials) */
53
+ name?: string;
54
+ /** Background color for fallback */
55
+ fallbackColor?: string;
56
+ }
57
+
58
+ export interface AvatarStackProps {
59
+ /** Array of avatar items */
60
+ avatars: AvatarItem[];
61
+ /** Maximum number of avatars to show */
62
+ max?: number;
63
+ /** Size preset */
64
+ size?: AvatarStackSize;
65
+ /** Overlap amount (0-1, default 0.3) */
66
+ overlap?: number;
67
+ /** Container style */
68
+ style?: ViewStyle;
69
+ }
70
+
71
+ // ─────────────────────────────────────────────────────────────────────────────
72
+ // Size configs
73
+ // ─────────────────────────────────────────────────────────────────────────────
74
+
75
+ const SIZE_CONFIG = {
76
+ sm: {
77
+ size: 28,
78
+ fontSize: 10,
79
+ borderWidth: 2,
80
+ },
81
+ md: {
82
+ size: 36,
83
+ fontSize: 12,
84
+ borderWidth: 2,
85
+ },
86
+ lg: {
87
+ size: 44,
88
+ fontSize: 14,
89
+ borderWidth: 3,
90
+ },
91
+ xl: {
92
+ size: 56,
93
+ fontSize: 18,
94
+ borderWidth: 3,
95
+ },
96
+ };
97
+
98
+ // ─────────────────────────────────────────────────────────────────────────────
99
+ // Helper functions
100
+ // ─────────────────────────────────────────────────────────────────────────────
101
+
102
+ function getInitials(name?: string): string {
103
+ if (!name) return '?';
104
+ const parts = name.trim().split(/\s+/);
105
+ const first = parts[0] ?? '';
106
+ const last = parts[parts.length - 1] ?? '';
107
+ if (parts.length === 1) {
108
+ return first.charAt(0).toUpperCase();
109
+ }
110
+ return (first.charAt(0) + last.charAt(0)).toUpperCase();
111
+ }
112
+
113
+ function stringToColor(str: string): string {
114
+ let hash = 0;
115
+ for (let i = 0; i < str.length; i++) {
116
+ hash = str.charCodeAt(i) + ((hash << 5) - hash);
117
+ }
118
+ const hue = Math.abs(hash % 360);
119
+ return `hsl(${hue}, 55%, 55%)`;
120
+ }
121
+
122
+ // ─────────────────────────────────────────────────────────────────────────────
123
+ // Component
124
+ // ─────────────────────────────────────────────────────────────────────────────
125
+
126
+ export function AvatarStack({
127
+ avatars,
128
+ max = 4,
129
+ size = 'md',
130
+ overlap = 0.3,
131
+ style,
132
+ }: AvatarStackProps) {
133
+ const { colors, fontWeight } = useTheme();
134
+ const config = SIZE_CONFIG[size];
135
+
136
+ const visibleAvatars = avatars.slice(0, max);
137
+ const overflowCount = avatars.length - max;
138
+ const hasOverflow = overflowCount > 0;
139
+
140
+ // Calculate overlap offset
141
+ const overlapOffset = config.size * overlap;
142
+
143
+ return (
144
+ <View
145
+ style={[
146
+ styles.container,
147
+ style,
148
+ ]}
149
+ accessibilityRole="none"
150
+ accessibilityLabel={`${avatars.length} users`}
151
+ >
152
+ {visibleAvatars.map((avatar, index) => {
153
+ const initials = getInitials(avatar.name);
154
+ const bgColor = avatar.fallbackColor || stringToColor(avatar.name || `user-${index}`);
155
+
156
+ return (
157
+ <View
158
+ key={index}
159
+ style={[
160
+ styles.avatarWrapper,
161
+ {
162
+ width: config.size,
163
+ height: config.size,
164
+ borderRadius: config.size / 2,
165
+ borderWidth: config.borderWidth,
166
+ borderColor: colors.background,
167
+ marginLeft: index === 0 ? 0 : -overlapOffset,
168
+ zIndex: visibleAvatars.length - index,
169
+ },
170
+ ]}
171
+ >
172
+ {avatar.source ? (
173
+ <Image
174
+ source={avatar.source}
175
+ style={[
176
+ styles.avatarImage,
177
+ {
178
+ width: config.size - config.borderWidth * 2,
179
+ height: config.size - config.borderWidth * 2,
180
+ borderRadius: (config.size - config.borderWidth * 2) / 2,
181
+ },
182
+ ]}
183
+ />
184
+ ) : (
185
+ <View
186
+ style={[
187
+ styles.avatarFallback,
188
+ {
189
+ width: config.size - config.borderWidth * 2,
190
+ height: config.size - config.borderWidth * 2,
191
+ borderRadius: (config.size - config.borderWidth * 2) / 2,
192
+ backgroundColor: bgColor,
193
+ },
194
+ ]}
195
+ >
196
+ <Text
197
+ style={[
198
+ styles.initials,
199
+ {
200
+ fontSize: config.fontSize,
201
+ fontWeight: fontWeight.semibold,
202
+ color: '#ffffff',
203
+ },
204
+ ]}
205
+ >
206
+ {initials}
207
+ </Text>
208
+ </View>
209
+ )}
210
+ </View>
211
+ );
212
+ })}
213
+
214
+ {/* Overflow indicator */}
215
+ {hasOverflow && (
216
+ <View
217
+ style={[
218
+ styles.avatarWrapper,
219
+ styles.overflowBadge,
220
+ {
221
+ width: config.size,
222
+ height: config.size,
223
+ borderRadius: config.size / 2,
224
+ borderWidth: config.borderWidth,
225
+ borderColor: colors.background,
226
+ backgroundColor: colors.secondary,
227
+ marginLeft: -overlapOffset,
228
+ zIndex: 0,
229
+ },
230
+ ]}
231
+ >
232
+ <Text
233
+ style={[
234
+ styles.overflowText,
235
+ {
236
+ fontSize: config.fontSize,
237
+ fontWeight: fontWeight.semibold,
238
+ color: colors.secondaryForeground,
239
+ },
240
+ ]}
241
+ >
242
+ +{overflowCount > 99 ? '99' : overflowCount}
243
+ </Text>
244
+ </View>
245
+ )}
246
+ </View>
247
+ );
248
+ }
249
+
250
+ // ─────────────────────────────────────────────────────────────────────────────
251
+ // Styles
252
+ // ─────────────────────────────────────────────────────────────────────────────
253
+
254
+ const styles = StyleSheet.create({
255
+ container: {
256
+ flexDirection: 'row',
257
+ alignItems: 'center',
258
+ },
259
+ avatarWrapper: {
260
+ justifyContent: 'center',
261
+ alignItems: 'center',
262
+ backgroundColor: '#ffffff',
263
+ },
264
+ avatarImage: {
265
+ resizeMode: 'cover',
266
+ },
267
+ avatarFallback: {
268
+ justifyContent: 'center',
269
+ alignItems: 'center',
270
+ },
271
+ initials: {
272
+ textAlign: 'center',
273
+ },
274
+ overflowBadge: {},
275
+ overflowText: {
276
+ textAlign: 'center',
277
+ },
278
+ });